fix: files navigation (#1084)

This commit is contained in:
Aman Harwara 2022-06-13 22:10:05 +05:30 committed by GitHub
parent 77137ecc34
commit 5d090572fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 144 additions and 120 deletions

View File

@ -68,7 +68,7 @@
"@standardnotes/icons": "workspace:*",
"@standardnotes/services": "^1.13.14",
"@standardnotes/sncrypto-web": "1.10.1",
"@standardnotes/snjs": "^2.115.9",
"@standardnotes/snjs": "^2.116.0",
"@standardnotes/styles": "workspace:*",
"@standardnotes/toast": "workspace:*",
"@zip.js/zip.js": "^2.4.10",

View File

@ -11,7 +11,7 @@ import {
DeinitSource,
Platform,
SNApplication,
NoteGroupController,
ItemGroupController,
removeFromArray,
IconsController,
DesktopDeviceInterface,
@ -41,7 +41,7 @@ export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
export class WebApplication extends SNApplication {
private webServices!: WebServices
private webEventObservers: WebEventObserver[] = []
public noteControllerGroup: NoteGroupController
public itemControllerGroup: ItemGroupController
public iconsController: IconsController
private onVisibilityChange: () => void
@ -70,7 +70,7 @@ export class WebApplication extends SNApplication {
})
deviceInterface.setApplication(this)
this.noteControllerGroup = new NoteGroupController(this)
this.itemControllerGroup = new ItemGroupController(this)
this.iconsController = new IconsController()
this.onVisibilityChange = () => {
@ -102,8 +102,8 @@ export class WebApplication extends SNApplication {
this.webServices = {} as WebServices
this.noteControllerGroup.deinit()
;(this.noteControllerGroup as unknown) = undefined
this.itemControllerGroup.deinit()
;(this.itemControllerGroup as unknown) = undefined
this.webEventObservers.length = 0

View File

@ -1,4 +1,4 @@
import { FileItem, NoteViewController } from '@standardnotes/snjs'
import { FileItem, FileViewController, NoteViewController } from '@standardnotes/snjs'
import { PureComponent } from '@/Components/Abstract/PureComponent'
import { WebApplication } from '@/Application/Application'
import MultipleSelectedNotes from '@/Components/MultipleSelectedNotes/MultipleSelectedNotes'
@ -10,7 +10,7 @@ import FileView from '@/Components/FileView/FileView'
type State = {
showMultipleSelectedNotes: boolean
showMultipleSelectedFiles: boolean
controllers: NoteViewController[]
controllers: (NoteViewController | FileViewController)[]
selectedFile: FileItem | undefined
}
@ -34,9 +34,9 @@ class NoteGroupView extends PureComponent<Props, State> {
override componentDidMount(): void {
super.componentDidMount()
const controllerGroup = this.application.noteControllerGroup
this.removeChangeObserver = this.application.noteControllerGroup.addActiveControllerChangeObserver(() => {
const controllers = controllerGroup.noteControllers
const controllerGroup = this.application.itemControllerGroup
this.removeChangeObserver = this.application.itemControllerGroup.addActiveControllerChangeObserver(() => {
const controllers = controllerGroup.itemControllers
this.setState({
controllers: controllers,
})
@ -110,18 +110,19 @@ class NoteGroupView extends PureComponent<Props, State> {
{shouldNotShowMultipleSelectedItems && this.state.controllers.length > 0 && (
<>
{this.state.controllers.map((controller) => {
return <NoteView key={controller.note.uuid} application={this.application} controller={controller} />
return controller instanceof NoteViewController ? (
<NoteView key={controller.item.uuid} application={this.application} controller={controller} />
) : (
<FileView
key={controller.item.uuid}
application={this.application}
viewControllerManager={this.viewControllerManager}
file={controller.item}
/>
)
})}
</>
)}
{shouldNotShowMultipleSelectedItems && this.state.controllers.length < 1 && this.state.selectedFile && (
<FileView
application={this.application}
viewControllerManager={this.viewControllerManager}
file={this.state.selectedFile}
/>
)}
</div>
)
}

View File

@ -52,7 +52,7 @@ describe('NoteView', () => {
it("should hide the note if at the time of the session expiration the note wasn't edited for longer than the allowed idle time", async () => {
const secondsElapsedSinceLastEdit = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction + 5
noteViewController.note = {
noteViewController.item = {
protected: true,
userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000),
} as jest.Mocked<SNNote>
@ -65,7 +65,7 @@ describe('NoteView', () => {
it('should postpone the note hiding by correct time if the time passed after its last modification is less than the allowed idle time', async () => {
const secondsElapsedSinceLastEdit = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - 3
noteViewController.note = {
noteViewController.item = {
protected: true,
userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000),
} as jest.Mocked<SNNote>
@ -87,7 +87,7 @@ describe('NoteView', () => {
it('should postpone the note hiding by correct time if the user continued editing it even after the protection session has expired', async () => {
const secondsElapsedSinceLastModification = 3
noteViewController.note = {
noteViewController.item = {
protected: true,
userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastModification * 1000),
} as jest.Mocked<SNNote>
@ -98,7 +98,7 @@ describe('NoteView', () => {
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastModification
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
noteViewController.note = {
noteViewController.item = {
protected: true,
userModifiedDate: new Date(),
} as jest.Mocked<SNNote>
@ -114,7 +114,7 @@ describe('NoteView', () => {
describe('note is unprotected', () => {
it('should not call any hiding logic', async () => {
noteViewController.note = {
noteViewController.item = {
protected: false,
} as jest.Mocked<SNNote>
@ -126,7 +126,7 @@ describe('NoteView', () => {
describe('dismissProtectedWarning', () => {
beforeEach(() => {
noteViewController.note = {
noteViewController.item = {
protected: false,
} as jest.Mocked<SNNote>
})

View File

@ -118,7 +118,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
isDesktop: isDesktopApplication(),
lockText: NOTE_EDITING_DISABLED_TEXT,
noteStatus: undefined,
noteLocked: this.controller.note.locked,
noteLocked: this.controller.item.locked,
showLockedIcon: true,
showProtectedWarning: false,
spellcheck: true,
@ -180,7 +180,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
}
get note() {
return this.controller.note
return this.controller.item
}
override componentDidMount(): void {

View File

@ -15,6 +15,8 @@ import {
InternalEventBus,
InternalEventHandlerInterface,
InternalEventInterface,
FileViewController,
FileItem,
} from '@standardnotes/snjs'
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
import { WebApplication } from '../../Application/Application'
@ -131,7 +133,7 @@ export class ItemListController extends AbstractViewController implements Intern
this.disposers.push(
application.addEventObserver(async () => {
this.application.noteControllerGroup.closeAllNoteControllers()
this.application.itemControllerGroup.closeAllItemControllers()
void this.selectFirstItem()
this.setCompletedFullSync(false)
}, ApplicationEvent.SignedIn),
@ -145,7 +147,7 @@ export class ItemListController extends AbstractViewController implements Intern
this.navigationController.selected instanceof SmartView &&
this.navigationController.selected.uuid === SystemViewId.AllNotes &&
this.noteFilterText === '' &&
!this.getActiveNoteController()
!this.getActiveItemController()
) {
this.createPlaceholderNote()?.catch(console.error)
}
@ -213,12 +215,45 @@ export class ItemListController extends AbstractViewController implements Intern
}
}
public getActiveNoteController(): NoteViewController | undefined {
return this.application.noteControllerGroup.activeNoteViewController
public getActiveItemController(): NoteViewController | FileViewController | undefined {
return this.application.itemControllerGroup.activeItemViewController
}
public get activeControllerNote(): SNNote | undefined {
return this.getActiveNoteController()?.note
const activeController = this.getActiveItemController()
return activeController instanceof NoteViewController ? activeController.item : undefined
}
async openNote(uuid: string): Promise<void> {
if (this.activeControllerNote?.uuid === uuid) {
return
}
const note = this.application.items.findItem<SNNote>(uuid)
if (!note) {
console.warn('Tried accessing a non-existant note of UUID ' + uuid)
return
}
await this.application.itemControllerGroup.createItemController(note)
this.noteTagsController.reloadTagsForCurrentNote()
await this.publishEventSync(CrossControllerEvent.ActiveEditorChanged)
}
async openFile(fileUuid: string): Promise<void> {
if (this.getActiveItemController()?.item.uuid === fileUuid) {
return
}
const file = this.application.items.findItem<FileItem>(fileUuid)
if (!file) {
console.warn('Tried accessing a non-existant file of UUID ' + fileUuid)
return
}
await this.application.itemControllerGroup.createItemController(file)
}
setCompletedFullSync = (completed: boolean) => {
@ -241,7 +276,7 @@ export class ItemListController extends AbstractViewController implements Intern
let title = this.panelTitle
if (this.isFiltering) {
const resultCount = this.notes.length
const resultCount = this.items.length
title = `${resultCount} search results`
} else if (this.navigationController.selected) {
title = `${this.navigationController.selected.title}`
@ -283,54 +318,59 @@ export class ItemListController extends AbstractViewController implements Intern
this.reloadPanelTitle()
}
private async recomputeSelectionAfterItemsReload(itemsReloadSource: ItemsReloadSource) {
const activeController = this.getActiveNoteController()
const activeNote = activeController?.note
const isSearching = this.noteFilterText.length > 0
private shouldLeaveSelectionUnchanged = (activeController: NoteViewController | FileViewController | undefined) => {
const hasMultipleItemsSelected = this.selectionController.selectedItemsCount >= 2
if (hasMultipleItemsSelected) {
return
}
return (
hasMultipleItemsSelected || (activeController instanceof NoteViewController && activeController.isTemplateNote)
)
}
if (itemsReloadSource === ItemsReloadSource.TagChange) {
await this.selectFirstItem()
private shouldSelectFirstItem = (itemsReloadSource: ItemsReloadSource, activeItem: SNNote | FileItem | undefined) => {
return itemsReloadSource === ItemsReloadSource.TagChange || !activeItem
}
return
}
private shouldCloseActiveItem = (activeItem: SNNote | FileItem | undefined) => {
const isSearching = this.noteFilterText.length > 0
const itemExistsInUpdatedResults = this.items.find((item) => item.uuid === activeItem?.uuid)
if (!activeNote) {
await this.selectFirstItem()
return !itemExistsInUpdatedResults && !isSearching && this.navigationController.isInAnySystemView()
}
return
}
if (activeController.isTemplateNote) {
return
}
const noteExistsInUpdatedResults = this.notes.find((note) => note.uuid === activeNote.uuid)
const shouldCloseActiveNote =
!noteExistsInUpdatedResults && !isSearching && this.navigationController.isInAnySystemView()
if (shouldCloseActiveNote) {
this.closeNoteController(activeController)
this.selectNextItem()
return
}
const showTrashedNotes =
private shouldSelectNextItemOrCreateNewNote = (activeItem: SNNote | FileItem | undefined) => {
const shouldShowTrashedNotes =
this.navigationController.isInSystemView(SystemViewId.TrashedNotes) || this.searchOptionsController.includeTrashed
const showArchivedNotes =
const shouldShowArchivedNotes =
this.navigationController.isInSystemView(SystemViewId.ArchivedNotes) ||
this.searchOptionsController.includeArchived ||
this.application.getPreference(PrefKey.NotesShowArchived, false)
if ((activeNote.trashed && !showTrashedNotes) || (activeNote.archived && !showArchivedNotes)) {
return (activeItem?.trashed && !shouldShowTrashedNotes) || (activeItem?.archived && !shouldShowArchivedNotes)
}
private shouldSelectActiveItem = (activeItem: SNNote | FileItem | undefined) => {
return activeItem && !this.selectionController.selectedItems[activeItem.uuid]
}
private async recomputeSelectionAfterItemsReload(itemsReloadSource: ItemsReloadSource) {
const activeController = this.getActiveItemController()
if (this.shouldLeaveSelectionUnchanged(activeController)) {
return
}
const activeItem = activeController?.item
if (this.shouldSelectFirstItem(itemsReloadSource, activeItem)) {
await this.selectFirstItem()
} else if (this.shouldCloseActiveItem(activeItem) && activeController) {
this.closeItemController(activeController)
this.selectNextItem()
} else if (this.shouldSelectNextItemOrCreateNewNote(activeItem)) {
await this.selectNextItemOrCreateNewNote()
} else if (!this.selectionController.selectedItems[activeNote.uuid]) {
await this.selectionController.selectItem(activeNote.uuid).catch(console.error)
} else if (this.shouldSelectActiveItem(activeItem) && activeItem) {
await this.selectionController.selectItem(activeItem.uuid).catch(console.error)
}
}
@ -423,6 +463,17 @@ export class ItemListController extends AbstractViewController implements Intern
}
}
async createNewNoteController(title?: string) {
const selectedTag = this.navigationController.selected
const activeRegularTagUuid = selectedTag instanceof SNTag ? selectedTag.uuid : undefined
await this.application.itemControllerGroup.createItemController({
title,
tag: activeRegularTagUuid,
})
}
createNewNote = async () => {
this.notesController.unselectNotes()
@ -435,7 +486,7 @@ export class ItemListController extends AbstractViewController implements Intern
title = this.noteFilterText
}
await this.notesController.createNewNoteController(title)
await this.createNewNoteController(title)
this.noteTagsController.reloadTagsForCurrentNote()
}
@ -622,7 +673,7 @@ export class ItemListController extends AbstractViewController implements Intern
}
handleEditorChange = async () => {
const activeNote = this.application.noteControllerGroup.activeNoteViewController?.note
const activeNote = this.application.itemControllerGroup.activeItemViewController?.item
if (activeNote && activeNote.conflictOf) {
this.application.mutator
@ -644,14 +695,14 @@ export class ItemListController extends AbstractViewController implements Intern
}
}
private closeNoteController(controller: NoteViewController): void {
this.application.noteControllerGroup.closeNoteController(controller)
private closeItemController(controller: NoteViewController | FileViewController): void {
this.application.itemControllerGroup.closeItemController(controller)
}
handleTagChange = () => {
const activeNoteController = this.getActiveNoteController()
if (activeNoteController?.isTemplateNote) {
this.closeNoteController(activeNoteController)
const activeNoteController = this.getActiveItemController()
if (activeNoteController instanceof NoteViewController && activeNoteController.isTemplateNote) {
this.closeItemController(activeNoteController)
}
this.resetScrollPosition()
@ -681,13 +732,13 @@ export class ItemListController extends AbstractViewController implements Intern
}
public async insertCurrentIfTemplate(): Promise<void> {
const controller = this.getActiveNoteController()
const controller = this.getActiveItemController()
if (!controller) {
return
}
if (controller.isTemplateNote) {
if (controller instanceof NoteViewController && controller.isTemplateNote) {
await controller.insertTemplatedNote()
}
}

View File

@ -10,7 +10,6 @@ import { SelectedItemsController } from './SelectedItemsController'
import { ItemListController } from './ItemList/ItemListController'
import { NoteTagsController } from './NoteTagsController'
import { NavigationController } from './Navigation/NavigationController'
import { CrossControllerEvent } from './CrossControllerEvent'
export class NotesController extends AbstractViewController {
lastSelectedNote: SNNote | undefined
@ -84,10 +83,10 @@ export class NotesController extends AbstractViewController {
})
}),
this.application.noteControllerGroup.addActiveControllerChangeObserver(() => {
const controllers = this.application.noteControllerGroup.noteControllers
this.application.itemControllerGroup.addActiveControllerChangeObserver(() => {
const controllers = this.application.itemControllerGroup.itemControllers
const activeNoteUuids = controllers.map((c) => c.note.uuid)
const activeNoteUuids = controllers.map((controller) => controller.item.uuid)
const selectedUuids = this.getSelectedNotesList().map((n) => n.uuid)
@ -120,32 +119,6 @@ export class NotesController extends AbstractViewController {
return this.application.items.trashedItems.length
}
async openNote(noteUuid: string): Promise<void> {
if (this.itemListController.activeControllerNote?.uuid === noteUuid) {
return
}
const note = this.application.items.findItem(noteUuid) as SNNote | undefined
if (!note) {
console.warn('Tried accessing a non-existant note of UUID ' + noteUuid)
return
}
await this.application.noteControllerGroup.createNoteController(noteUuid)
this.noteTagsController.reloadTagsForCurrentNote()
await this.publishEventSync(CrossControllerEvent.ActiveEditorChanged)
}
async createNewNoteController(title?: string) {
const selectedTag = this.navigationController.selected
const activeRegularTagUuid = selectedTag && selectedTag instanceof SNTag ? selectedTag.uuid : undefined
await this.application.noteControllerGroup.createNoteController(undefined, title, activeRegularTagUuid)
}
setContextMenuOpen(open: boolean): void {
this.contextMenuOpen = open
}

View File

@ -12,7 +12,6 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { ItemListController } from './ItemList/ItemListController'
import { NotesController } from './NotesController'
type SelectedItems = Record<UuidString, ListableContentItem>
@ -20,12 +19,10 @@ export class SelectedItemsController extends AbstractViewController {
lastSelectedItem: ListableContentItem | undefined
selectedItems: SelectedItems = {}
private itemListController!: ItemListController
private notesController!: NotesController
override deinit(): void {
super.deinit()
;(this.itemListController as unknown) = undefined
;(this.notesController as unknown) = undefined
}
constructor(application: WebApplication, eventBus: InternalEventBus) {
@ -43,9 +40,8 @@ export class SelectedItemsController extends AbstractViewController {
})
}
public setServicesPostConstruction(itemListController: ItemListController, notesController: NotesController) {
public setServicesPostConstruction(itemListController: ItemListController) {
this.itemListController = itemListController
this.notesController = notesController
this.disposers.push(
this.application.streamItems<SNNote | FileItem>(
@ -198,8 +194,11 @@ export class SelectedItemsController extends AbstractViewController {
if (this.selectedItemsCount === 1) {
const item = Object.values(this.selectedItems)[0]
if (item.content_type === ContentType.Note) {
await this.notesController.openNote(item.uuid)
await this.itemListController.openNote(item.uuid)
} else if (item.content_type === ContentType.File) {
await this.itemListController.openFile(item.uuid)
}
}

View File

@ -84,7 +84,7 @@ export class ViewControllerManager {
this.notesController.setServicesPostConstruction(this.itemListController)
this.noteTagsController.setServicesPostConstruction(this.itemListController)
this.selectionController.setServicesPostConstruction(this.itemListController, this.notesController)
this.selectionController.setServicesPostConstruction(this.itemListController)
this.noAccountWarningController = new NoAccountWarningController(application, this.eventBus)

View File

@ -5378,9 +5378,9 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/snjs@npm:^2.115.9, @standardnotes/snjs@npm:^2.41.1":
version: 2.115.12
resolution: "@standardnotes/snjs@npm:2.115.12"
"@standardnotes/snjs@npm:^2.116.0, @standardnotes/snjs@npm:^2.41.1":
version: 2.116.0
resolution: "@standardnotes/snjs@npm:2.116.0"
dependencies:
"@standardnotes/api": ^1.1.8
"@standardnotes/auth": ^3.19.2
@ -5395,7 +5395,7 @@ __metadata:
"@standardnotes/settings": ^1.14.3
"@standardnotes/sncrypto-common": ^1.9.0
"@standardnotes/utils": ^1.6.10
checksum: 808bca0c48ca995d678ddc14120f21f144689670f451b2a88be86c9c4d424efadd41fff0648e4a0542a4b000de30331a1486533e9dec70233a4fcab41f7181e7
checksum: 27e9eff8214def10c299471a318d06fbfbf07fd780819b9f75e6415aa5220d065c57443274bcc8e3ff9def0348950eabdf2ed636724bc01ece72a7b90af5f660
languageName: node
linkType: hard
@ -5530,7 +5530,7 @@ __metadata:
"@standardnotes/icons": "workspace:*"
"@standardnotes/services": ^1.13.14
"@standardnotes/sncrypto-web": 1.10.1
"@standardnotes/snjs": ^2.115.9
"@standardnotes/snjs": ^2.116.0
"@standardnotes/styles": "workspace:*"
"@standardnotes/toast": "workspace:*"
"@types/jest": ^27.4.1