feat: prioritize loading latest selected items (#1930)
This commit is contained in:
@@ -38,6 +38,7 @@ export enum StorageKey {
|
|||||||
ExperimentalFeatures = 'experimental_features',
|
ExperimentalFeatures = 'experimental_features',
|
||||||
DeinitMode = 'deinit_mode',
|
DeinitMode = 'deinit_mode',
|
||||||
CodeVerifier = 'code_verifier',
|
CodeVerifier = 'code_verifier',
|
||||||
|
LaunchPriorityUuids = 'launch_priority_uuids',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NonwrappedStorageKey {
|
export enum NonwrappedStorageKey {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class SNPreferencesService
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.removeSyncObserver = syncService.addEventObserver((event) => {
|
this.removeSyncObserver = syncService.addEventObserver((event) => {
|
||||||
if (event === SyncEvent.SyncCompletedWithAllItemsUploaded) {
|
if (event === SyncEvent.SyncCompletedWithAllItemsUploaded || event === SyncEvent.LocalDataIncrementalLoad) {
|
||||||
void this.reload()
|
void this.reload()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { SyncOpStatus } from './SyncOpStatus'
|
|||||||
import { SyncOptions } from '@standardnotes/services'
|
import { SyncOptions } from '@standardnotes/services'
|
||||||
|
|
||||||
export interface SyncClientInterface {
|
export interface SyncClientInterface {
|
||||||
|
setLaunchPriorityUuids(launchPriorityUuids: string[]): void
|
||||||
|
|
||||||
sync(options?: Partial<SyncOptions>): Promise<unknown>
|
sync(options?: Partial<SyncOptions>): Promise<unknown>
|
||||||
|
|
||||||
isOutOfSync(): boolean
|
isOutOfSync(): boolean
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { SNHistoryManager } from '../History/HistoryManager'
|
|||||||
import { SNLog } from '@Lib/Log'
|
import { SNLog } from '@Lib/Log'
|
||||||
import { SNSessionManager } from '../Session/SessionManager'
|
import { SNSessionManager } from '../Session/SessionManager'
|
||||||
import { DiskStorageService } from '../Storage/DiskStorageService'
|
import { DiskStorageService } from '../Storage/DiskStorageService'
|
||||||
import { SortPayloadsByRecentAndContentPriority } from '@Lib/Services/Sync/Utils'
|
import { GetSortedPayloadsByPriority } from '@Lib/Services/Sync/Utils'
|
||||||
import { SyncClientInterface } from './SyncClientInterface'
|
import { SyncClientInterface } from './SyncClientInterface'
|
||||||
import { SyncPromise } from './Types'
|
import { SyncPromise } from './Types'
|
||||||
import { SyncOpStatus } from '@Lib/Services/Sync/SyncOpStatus'
|
import { SyncOpStatus } from '@Lib/Services/Sync/SyncOpStatus'
|
||||||
@@ -56,6 +56,7 @@ import {
|
|||||||
PayloadEmitSource,
|
PayloadEmitSource,
|
||||||
getIncrementedDirtyIndex,
|
getIncrementedDirtyIndex,
|
||||||
getCurrentDirtyIndex,
|
getCurrentDirtyIndex,
|
||||||
|
ItemContent,
|
||||||
} from '@standardnotes/models'
|
} from '@standardnotes/models'
|
||||||
import {
|
import {
|
||||||
AbstractService,
|
AbstractService,
|
||||||
@@ -158,6 +159,14 @@ export class SNSyncService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get launchPriorityUuids() {
|
||||||
|
return this.storageService.getValue<string[]>(StorageKey.LaunchPriorityUuids) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLaunchPriorityUuids(launchPriorityUuids: string[]) {
|
||||||
|
this.storageService.setValue(StorageKey.LaunchPriorityUuids, launchPriorityUuids)
|
||||||
|
}
|
||||||
|
|
||||||
public override deinit(): void {
|
public override deinit(): void {
|
||||||
this.dealloced = true
|
this.dealloced = true
|
||||||
;(this.sessionManager as unknown) = undefined
|
;(this.sessionManager as unknown) = undefined
|
||||||
@@ -272,15 +281,15 @@ export class SNSyncService
|
|||||||
})
|
})
|
||||||
.filter(isNotUndefined)
|
.filter(isNotUndefined)
|
||||||
|
|
||||||
const payloads = SortPayloadsByRecentAndContentPriority(unsortedPayloads, this.localLoadPriorty)
|
const { itemsKeyPayloads, contentTypePriorityPayloads, remainingPayloads } = GetSortedPayloadsByPriority(
|
||||||
|
unsortedPayloads,
|
||||||
|
this.localLoadPriorty,
|
||||||
|
this.launchPriorityUuids,
|
||||||
|
)
|
||||||
|
|
||||||
const itemsKeysPayloads = payloads.filter((payload) => {
|
await this.processItemsKeysFirstDuringDatabaseLoad(itemsKeyPayloads)
|
||||||
return payload.content_type === ContentType.ItemsKey
|
|
||||||
})
|
|
||||||
|
|
||||||
subtractFromArray(payloads, itemsKeysPayloads)
|
await this.processPayloadBatch(contentTypePriorityPayloads)
|
||||||
|
|
||||||
await this.processItemsKeysFirstDuringDatabaseLoad(itemsKeysPayloads)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map in batches to give interface a chance to update. Note that total decryption
|
* Map in batches to give interface a chance to update. Note that total decryption
|
||||||
@@ -288,45 +297,55 @@ export class SNSyncService
|
|||||||
* batches will result in the same time spent. It's the emitting/painting/rendering
|
* batches will result in the same time spent. It's the emitting/painting/rendering
|
||||||
* that requires batch size optimization.
|
* that requires batch size optimization.
|
||||||
*/
|
*/
|
||||||
const payloadCount = payloads.length
|
const payloadCount = remainingPayloads.length
|
||||||
const batchSize = this.options.loadBatchSize
|
const batchSize = this.options.loadBatchSize
|
||||||
const numBatches = Math.ceil(payloadCount / batchSize)
|
const numBatches = Math.ceil(payloadCount / batchSize)
|
||||||
|
|
||||||
for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) {
|
for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) {
|
||||||
const currentPosition = batchIndex * batchSize
|
const currentPosition = batchIndex * batchSize
|
||||||
const batch = payloads.slice(currentPosition, currentPosition + batchSize)
|
const batch = remainingPayloads.slice(currentPosition, currentPosition + batchSize)
|
||||||
const encrypted: EncryptedPayloadInterface[] = []
|
await this.processPayloadBatch(batch, currentPosition, payloadCount)
|
||||||
const nonencrypted: (DecryptedPayloadInterface | DeletedPayloadInterface)[] = []
|
|
||||||
|
|
||||||
for (const payload of batch) {
|
|
||||||
if (isEncryptedPayload(payload)) {
|
|
||||||
encrypted.push(payload)
|
|
||||||
} else {
|
|
||||||
nonencrypted.push(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const split: KeyedDecryptionSplit = {
|
|
||||||
usesItemsKeyWithKeyLookup: {
|
|
||||||
items: encrypted,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await this.protocolService.decryptSplit(split)
|
|
||||||
|
|
||||||
await this.payloadManager.emitPayloads([...nonencrypted, ...results], PayloadEmitSource.LocalDatabaseLoaded)
|
|
||||||
|
|
||||||
void this.notifyEvent(SyncEvent.LocalDataIncrementalLoad)
|
|
||||||
|
|
||||||
this.opStatus.setDatabaseLoadStatus(currentPosition, payloadCount, false)
|
|
||||||
|
|
||||||
await sleep(1, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.databaseLoaded = true
|
this.databaseLoaded = true
|
||||||
this.opStatus.setDatabaseLoadStatus(0, 0, true)
|
this.opStatus.setDatabaseLoadStatus(0, 0, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async processPayloadBatch(
|
||||||
|
batch: FullyFormedPayloadInterface<ItemContent>[],
|
||||||
|
currentPosition?: number,
|
||||||
|
payloadCount?: number,
|
||||||
|
) {
|
||||||
|
const encrypted: EncryptedPayloadInterface[] = []
|
||||||
|
const nonencrypted: (DecryptedPayloadInterface | DeletedPayloadInterface)[] = []
|
||||||
|
|
||||||
|
for (const payload of batch) {
|
||||||
|
if (isEncryptedPayload(payload)) {
|
||||||
|
encrypted.push(payload)
|
||||||
|
} else {
|
||||||
|
nonencrypted.push(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const split: KeyedDecryptionSplit = {
|
||||||
|
usesItemsKeyWithKeyLookup: {
|
||||||
|
items: encrypted,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await this.protocolService.decryptSplit(split)
|
||||||
|
|
||||||
|
await this.payloadManager.emitPayloads([...nonencrypted, ...results], PayloadEmitSource.LocalDatabaseLoaded)
|
||||||
|
|
||||||
|
void this.notifyEvent(SyncEvent.LocalDataIncrementalLoad)
|
||||||
|
|
||||||
|
if (currentPosition != undefined && payloadCount != undefined) {
|
||||||
|
this.opStatus.setDatabaseLoadStatus(currentPosition, payloadCount, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(1, false)
|
||||||
|
}
|
||||||
|
|
||||||
private setLastSyncToken(token: string) {
|
private setLastSyncToken(token: string) {
|
||||||
this.syncToken = token
|
this.syncToken = token
|
||||||
return this.storageService.setValue(StorageKey.LastSyncToken, token)
|
return this.storageService.setValue(StorageKey.LastSyncToken, token)
|
||||||
|
|||||||
146
packages/snjs/lib/Services/Sync/Utils.spec.ts
Normal file
146
packages/snjs/lib/Services/Sync/Utils.spec.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import { ContentType } from '@standardnotes/common'
|
||||||
|
import { FullyFormedPayloadInterface } from '@standardnotes/models'
|
||||||
|
import { GetSortedPayloadsByPriority } from './Utils'
|
||||||
|
|
||||||
|
describe('GetSortedPayloadsByPriority', () => {
|
||||||
|
let payloads: FullyFormedPayloadInterface[] = []
|
||||||
|
const contentTypePriority = [ContentType.ItemsKey, ContentType.UserPrefs, ContentType.Component, ContentType.Theme]
|
||||||
|
let launchPriorityUuids: string[] = []
|
||||||
|
|
||||||
|
it('should sort payloads based on content type priority', () => {
|
||||||
|
payloads = [
|
||||||
|
{
|
||||||
|
content_type: ContentType.Theme,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.UserPrefs,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Component,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.ItemsKey,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Note,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
]
|
||||||
|
|
||||||
|
const { itemsKeyPayloads, contentTypePriorityPayloads, remainingPayloads } = GetSortedPayloadsByPriority(
|
||||||
|
payloads,
|
||||||
|
contentTypePriority,
|
||||||
|
launchPriorityUuids,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(itemsKeyPayloads.length).toBe(1)
|
||||||
|
expect(itemsKeyPayloads[0].content_type).toBe(ContentType.ItemsKey)
|
||||||
|
|
||||||
|
expect(contentTypePriorityPayloads.length).toBe(3)
|
||||||
|
expect(contentTypePriorityPayloads[0].content_type).toBe(ContentType.UserPrefs)
|
||||||
|
expect(contentTypePriorityPayloads[1].content_type).toBe(ContentType.Component)
|
||||||
|
expect(contentTypePriorityPayloads[2].content_type).toBe(ContentType.Theme)
|
||||||
|
|
||||||
|
expect(remainingPayloads.length).toBe(1)
|
||||||
|
expect(remainingPayloads[0].content_type).toBe(ContentType.Note)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should sort payloads based on launch priority uuids', () => {
|
||||||
|
const unprioritizedNoteUuid = 'unprioritized-note'
|
||||||
|
const unprioritizedTagUuid = 'unprioritized-tag'
|
||||||
|
|
||||||
|
const prioritizedNoteUuid = 'prioritized-note'
|
||||||
|
const prioritizedTagUuid = 'prioritized-tag'
|
||||||
|
|
||||||
|
payloads = [
|
||||||
|
{
|
||||||
|
content_type: ContentType.Theme,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.UserPrefs,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Component,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.ItemsKey,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Note,
|
||||||
|
uuid: unprioritizedNoteUuid,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Tag,
|
||||||
|
uuid: unprioritizedTagUuid,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Note,
|
||||||
|
uuid: prioritizedNoteUuid,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Tag,
|
||||||
|
uuid: prioritizedTagUuid,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
]
|
||||||
|
|
||||||
|
launchPriorityUuids = [prioritizedNoteUuid, prioritizedTagUuid]
|
||||||
|
|
||||||
|
const { itemsKeyPayloads, contentTypePriorityPayloads, remainingPayloads } = GetSortedPayloadsByPriority(
|
||||||
|
payloads,
|
||||||
|
contentTypePriority,
|
||||||
|
launchPriorityUuids,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(itemsKeyPayloads.length).toBe(1)
|
||||||
|
expect(itemsKeyPayloads[0].content_type).toBe(ContentType.ItemsKey)
|
||||||
|
|
||||||
|
expect(contentTypePriorityPayloads.length).toBe(3)
|
||||||
|
expect(contentTypePriorityPayloads[0].content_type).toBe(ContentType.UserPrefs)
|
||||||
|
expect(contentTypePriorityPayloads[1].content_type).toBe(ContentType.Component)
|
||||||
|
expect(contentTypePriorityPayloads[2].content_type).toBe(ContentType.Theme)
|
||||||
|
|
||||||
|
expect(remainingPayloads.length).toBe(4)
|
||||||
|
expect(remainingPayloads[0].uuid).toBe(prioritizedNoteUuid)
|
||||||
|
expect(remainingPayloads[1].uuid).toBe(prioritizedTagUuid)
|
||||||
|
expect(remainingPayloads[2].uuid).toBe(unprioritizedNoteUuid)
|
||||||
|
expect(remainingPayloads[3].uuid).toBe(unprioritizedTagUuid)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should sort payloads based on server updated date if same content type', () => {
|
||||||
|
const unprioritizedNoteUuid = 'unprioritized-note'
|
||||||
|
const unprioritizedTagUuid = 'unprioritized-tag'
|
||||||
|
|
||||||
|
const prioritizedNoteUuid = 'prioritized-note'
|
||||||
|
const prioritizedTagUuid = 'prioritized-tag'
|
||||||
|
|
||||||
|
payloads = [
|
||||||
|
{
|
||||||
|
content_type: ContentType.Note,
|
||||||
|
uuid: unprioritizedNoteUuid,
|
||||||
|
serverUpdatedAt: new Date(1),
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Tag,
|
||||||
|
uuid: unprioritizedTagUuid,
|
||||||
|
serverUpdatedAt: new Date(2),
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Note,
|
||||||
|
uuid: prioritizedNoteUuid,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
{
|
||||||
|
content_type: ContentType.Tag,
|
||||||
|
uuid: prioritizedTagUuid,
|
||||||
|
} as FullyFormedPayloadInterface,
|
||||||
|
]
|
||||||
|
|
||||||
|
launchPriorityUuids = [prioritizedNoteUuid, prioritizedTagUuid]
|
||||||
|
|
||||||
|
const { remainingPayloads } = GetSortedPayloadsByPriority(payloads, contentTypePriority, launchPriorityUuids)
|
||||||
|
|
||||||
|
expect(remainingPayloads.length).toBe(4)
|
||||||
|
expect(remainingPayloads[0].uuid).toBe(prioritizedNoteUuid)
|
||||||
|
expect(remainingPayloads[1].uuid).toBe(prioritizedTagUuid)
|
||||||
|
expect(remainingPayloads[2].uuid).toBe(unprioritizedTagUuid)
|
||||||
|
expect(remainingPayloads[3].uuid).toBe(unprioritizedNoteUuid)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { UuidString } from '@Lib/Types'
|
||||||
import { ContentType } from '@standardnotes/common'
|
import { ContentType } from '@standardnotes/common'
|
||||||
import { FullyFormedPayloadInterface } from '@standardnotes/models'
|
import { FullyFormedPayloadInterface } from '@standardnotes/models'
|
||||||
|
|
||||||
@@ -6,9 +7,9 @@ import { FullyFormedPayloadInterface } from '@standardnotes/models'
|
|||||||
* whereby the earlier a content_type appears in the priorityList,
|
* whereby the earlier a content_type appears in the priorityList,
|
||||||
* the earlier it will appear in the resulting sorted array.
|
* the earlier it will appear in the resulting sorted array.
|
||||||
*/
|
*/
|
||||||
export function SortPayloadsByRecentAndContentPriority(
|
function SortPayloadsByRecentAndContentPriority(
|
||||||
payloads: FullyFormedPayloadInterface[],
|
payloads: FullyFormedPayloadInterface[],
|
||||||
priorityList: ContentType[],
|
contentTypePriorityList: ContentType[],
|
||||||
): FullyFormedPayloadInterface[] {
|
): FullyFormedPayloadInterface[] {
|
||||||
return payloads.sort((a, b) => {
|
return payloads.sort((a, b) => {
|
||||||
const dateResult = new Date(b.serverUpdatedAt).getTime() - new Date(a.serverUpdatedAt).getTime()
|
const dateResult = new Date(b.serverUpdatedAt).getTime() - new Date(a.serverUpdatedAt).getTime()
|
||||||
@@ -16,18 +17,15 @@ export function SortPayloadsByRecentAndContentPriority(
|
|||||||
let aPriority = 0
|
let aPriority = 0
|
||||||
let bPriority = 0
|
let bPriority = 0
|
||||||
|
|
||||||
if (priorityList) {
|
aPriority = contentTypePriorityList.indexOf(a.content_type)
|
||||||
aPriority = priorityList.indexOf(a.content_type)
|
bPriority = contentTypePriorityList.indexOf(b.content_type)
|
||||||
bPriority = priorityList.indexOf(b.content_type)
|
|
||||||
|
|
||||||
if (aPriority === -1) {
|
if (aPriority === -1) {
|
||||||
/** Not found in list, not prioritized. Set it to max value */
|
aPriority = contentTypePriorityList.length
|
||||||
aPriority = priorityList.length
|
}
|
||||||
}
|
|
||||||
if (bPriority === -1) {
|
if (bPriority === -1) {
|
||||||
/** Not found in list, not prioritized. Set it to max value */
|
bPriority = contentTypePriorityList.length
|
||||||
bPriority = priorityList.length
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aPriority === bPriority) {
|
if (aPriority === bPriority) {
|
||||||
@@ -41,3 +39,76 @@ export function SortPayloadsByRecentAndContentPriority(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts payloads according by most recently modified first, according to the priority,
|
||||||
|
* whereby the earlier a uuid appears in the priorityList,
|
||||||
|
* the earlier it will appear in the resulting sorted array.
|
||||||
|
*/
|
||||||
|
function SortPayloadsByRecentAndUuidPriority(
|
||||||
|
payloads: FullyFormedPayloadInterface[],
|
||||||
|
uuidPriorityList: UuidString[],
|
||||||
|
): FullyFormedPayloadInterface[] {
|
||||||
|
return payloads.sort((a, b) => {
|
||||||
|
const dateResult = new Date(b.serverUpdatedAt).getTime() - new Date(a.serverUpdatedAt).getTime()
|
||||||
|
|
||||||
|
let aPriority = 0
|
||||||
|
let bPriority = 0
|
||||||
|
|
||||||
|
aPriority = uuidPriorityList.indexOf(a.uuid)
|
||||||
|
bPriority = uuidPriorityList.indexOf(b.uuid)
|
||||||
|
|
||||||
|
if (aPriority === -1) {
|
||||||
|
aPriority = uuidPriorityList.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bPriority === -1) {
|
||||||
|
bPriority = uuidPriorityList.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aPriority === bPriority) {
|
||||||
|
return dateResult
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aPriority < bPriority) {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetSortedPayloadsByPriority(
|
||||||
|
payloads: FullyFormedPayloadInterface[],
|
||||||
|
contentTypePriorityList: ContentType[],
|
||||||
|
uuidPriorityList: UuidString[],
|
||||||
|
): {
|
||||||
|
itemsKeyPayloads: FullyFormedPayloadInterface[]
|
||||||
|
contentTypePriorityPayloads: FullyFormedPayloadInterface[]
|
||||||
|
remainingPayloads: FullyFormedPayloadInterface[]
|
||||||
|
} {
|
||||||
|
const itemsKeyPayloads: FullyFormedPayloadInterface[] = []
|
||||||
|
const contentTypePriorityPayloads: FullyFormedPayloadInterface[] = []
|
||||||
|
const remainingPayloads: FullyFormedPayloadInterface[] = []
|
||||||
|
|
||||||
|
for (let index = 0; index < payloads.length; index++) {
|
||||||
|
const payload = payloads[index]
|
||||||
|
|
||||||
|
if (payload.content_type === ContentType.ItemsKey) {
|
||||||
|
itemsKeyPayloads.push(payload)
|
||||||
|
} else if (contentTypePriorityList.includes(payload.content_type)) {
|
||||||
|
contentTypePriorityPayloads.push(payload)
|
||||||
|
} else {
|
||||||
|
remainingPayloads.push(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
itemsKeyPayloads,
|
||||||
|
contentTypePriorityPayloads: SortPayloadsByRecentAndContentPriority(
|
||||||
|
contentTypePriorityPayloads,
|
||||||
|
contentTypePriorityList,
|
||||||
|
),
|
||||||
|
remainingPayloads: SortPayloadsByRecentAndUuidPriority(remainingPayloads, uuidPriorityList),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -672,10 +672,10 @@ describe('online syncing', function () {
|
|||||||
const payload = Factory.createStorageItemPayload(contentTypes[Math.floor(i / 2)])
|
const payload = Factory.createStorageItemPayload(contentTypes[Math.floor(i / 2)])
|
||||||
originalPayloads.push(payload)
|
originalPayloads.push(payload)
|
||||||
}
|
}
|
||||||
const sorted = SortPayloadsByRecentAndContentPriority(originalPayloads, ['C', 'A', 'B'])
|
const { contentTypePriorityPayloads } = GetSortedPayloadsByPriority(originalPayloads, ['C', 'A', 'B'])
|
||||||
expect(sorted[0].content_type).to.equal('C')
|
expect(contentTypePriorityPayloads[0].content_type).to.equal('C')
|
||||||
expect(sorted[2].content_type).to.equal('A')
|
expect(contentTypePriorityPayloads[2].content_type).to.equal('A')
|
||||||
expect(sorted[4].content_type).to.equal('B')
|
expect(contentTypePriorityPayloads[4].content_type).to.equal('B')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should sign in and retrieve large number of items', async function () {
|
it('should sign in and retrieve large number of items', async function () {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export enum PersistenceKey {
|
||||||
|
SelectedItemsController = 'selected-items-controller',
|
||||||
|
NavigationController = 'navigation-controller',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectionControllerPersistableValue = {
|
||||||
|
selectedUuids: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NavigationControllerPersistableValue = {
|
||||||
|
selectedTagUuid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PersistedStateValue = {
|
||||||
|
[PersistenceKey.SelectedItemsController]: SelectionControllerPersistableValue
|
||||||
|
[PersistenceKey.NavigationController]: NavigationControllerPersistableValue
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { PersistedStateValue } from '../StatePersistence/StatePersistence'
|
||||||
|
|
||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
AnonymousUserId = 'AnonymousUserId',
|
AnonymousUserId = 'AnonymousUserId',
|
||||||
ShowBetaWarning = 'ShowBetaWarning',
|
ShowBetaWarning = 'ShowBetaWarning',
|
||||||
ShowNoAccountWarning = 'ShowNoAccountWarning',
|
ShowNoAccountWarning = 'ShowNoAccountWarning',
|
||||||
FilesNavigationEnabled = 'FilesNavigationEnabled',
|
FilesNavigationEnabled = 'FilesNavigationEnabled',
|
||||||
|
MasterStatePersistenceKey = 'master-persistence-key',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StorageValue = {
|
export type StorageValue = {
|
||||||
@@ -10,6 +13,7 @@ export type StorageValue = {
|
|||||||
[StorageKey.ShowBetaWarning]: boolean
|
[StorageKey.ShowBetaWarning]: boolean
|
||||||
[StorageKey.ShowNoAccountWarning]: boolean
|
[StorageKey.ShowNoAccountWarning]: boolean
|
||||||
[StorageKey.FilesNavigationEnabled]: boolean
|
[StorageKey.FilesNavigationEnabled]: boolean
|
||||||
|
[StorageKey.MasterStatePersistenceKey]: PersistedStateValue
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storage = {
|
export const storage = {
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ export * from './Storage/LocalStorage'
|
|||||||
export * from './Theme/ThemeManager'
|
export * from './Theme/ThemeManager'
|
||||||
export * from './Toast/ToastService'
|
export * from './Toast/ToastService'
|
||||||
export * from './Toast/ToastServiceInterface'
|
export * from './Toast/ToastServiceInterface'
|
||||||
|
export * from './StatePersistence/StatePersistence'
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
|||||||
void changePreferences({ sortBy: sort })
|
void changePreferences({ sortBy: sort })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[preferences, changePreferences, toggleSortReverse],
|
[preferences.sortBy, toggleSortReverse, changePreferences],
|
||||||
)
|
)
|
||||||
|
|
||||||
const toggleSortByDateModified = useCallback(() => {
|
const toggleSortByDateModified = useCallback(() => {
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ const Persistence = ({ application }: Props) => {
|
|||||||
const toggleStatePersistence = (shouldPersist: boolean) => {
|
const toggleStatePersistence = (shouldPersist: boolean) => {
|
||||||
application.setValue(ShouldPersistNoteStateKey, shouldPersist)
|
application.setValue(ShouldPersistNoteStateKey, shouldPersist)
|
||||||
setShouldPersistNoteState(shouldPersist)
|
setShouldPersistNoteState(shouldPersist)
|
||||||
|
|
||||||
|
if (shouldPersist) {
|
||||||
|
application.getViewControllerManager().persistValues()
|
||||||
|
} else {
|
||||||
|
application.getViewControllerManager().clearPersistedValues()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { ShouldPersistNoteStateKey } from '@/Components/Preferences/Panes/General/Persistence'
|
import { ShouldPersistNoteStateKey } from '@/Components/Preferences/Panes/General/Persistence'
|
||||||
import { ApplicationEvent, InternalEventBus } from '@standardnotes/snjs'
|
import { ApplicationEvent, ContentType, InternalEventBus } from '@standardnotes/snjs'
|
||||||
|
import { PersistedStateValue, StorageKey } from '@standardnotes/ui-services'
|
||||||
import { CrossControllerEvent } from '../CrossControllerEvent'
|
import { CrossControllerEvent } from '../CrossControllerEvent'
|
||||||
|
|
||||||
const MasterPersistenceKey = 'master-persistence-key'
|
|
||||||
|
|
||||||
export enum PersistenceKey {
|
|
||||||
SelectedItemsController = 'selected-items-controller',
|
|
||||||
NavigationController = 'navigation-controller',
|
|
||||||
ItemListController = 'item-list-controller',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MasterPersistedValue = Record<PersistenceKey, unknown>
|
|
||||||
|
|
||||||
export class PersistenceService {
|
export class PersistenceService {
|
||||||
private unsubAppEventObserver: () => void
|
private unsubAppEventObserver: () => void
|
||||||
|
private didHydrateOnce = false
|
||||||
|
|
||||||
constructor(private application: WebApplication, private eventBus: InternalEventBus) {
|
constructor(private application: WebApplication, private eventBus: InternalEventBus) {
|
||||||
this.unsubAppEventObserver = this.application.addEventObserver(async (eventName) => {
|
this.unsubAppEventObserver = this.application.addEventObserver(async (eventName) => {
|
||||||
@@ -27,31 +19,54 @@ export class PersistenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onAppEvent(eventName: ApplicationEvent) {
|
async onAppEvent(eventName: ApplicationEvent) {
|
||||||
if (eventName === ApplicationEvent.LocalDataLoaded) {
|
if (eventName === ApplicationEvent.LocalDataLoaded && !this.didHydrateOnce) {
|
||||||
let shouldHydrateState = this.application.getValue(ShouldPersistNoteStateKey)
|
this.hydratePersistedValues()
|
||||||
|
this.didHydrateOnce = true
|
||||||
|
} else if (eventName === ApplicationEvent.LocalDataIncrementalLoad) {
|
||||||
|
const canHydrate = this.application.items.getItems([ContentType.Note, ContentType.Tag]).length > 0
|
||||||
|
|
||||||
if (typeof shouldHydrateState === 'undefined') {
|
if (!canHydrate) {
|
||||||
this.application.setValue(ShouldPersistNoteStateKey, true)
|
return
|
||||||
shouldHydrateState = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventBus.publish({
|
this.hydratePersistedValues()
|
||||||
type: CrossControllerEvent.HydrateFromPersistedValues,
|
this.didHydrateOnce = true
|
||||||
payload: shouldHydrateState ? this.getPersistedValues() : undefined,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
persistValues(values: MasterPersistedValue): void {
|
get persistenceEnabled() {
|
||||||
|
return this.application.getValue(ShouldPersistNoteStateKey) ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
hydratePersistedValues = () => {
|
||||||
|
this.eventBus.publish({
|
||||||
|
type: CrossControllerEvent.HydrateFromPersistedValues,
|
||||||
|
payload: this.persistenceEnabled ? this.getPersistedValues() : undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
persistValues(values: PersistedStateValue): void {
|
||||||
if (!this.application.isDatabaseLoaded()) {
|
if (!this.application.isDatabaseLoaded()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.application.setValue(MasterPersistenceKey, values)
|
if (!this.persistenceEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.application.setValue(StorageKey.MasterStatePersistenceKey, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
getPersistedValues(): MasterPersistedValue {
|
clearPersistedValues(): void {
|
||||||
return this.application.getValue(MasterPersistenceKey) as MasterPersistedValue
|
if (!this.application.isDatabaseLoaded()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.application.removeValue(StorageKey.MasterStatePersistenceKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPersistedValues(): PersistedStateValue {
|
||||||
|
return this.application.getValue(StorageKey.MasterStatePersistenceKey) as PersistedStateValue
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import { PrefDefaults } from '@/Constants/PrefDefaults'
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { LinkingController } from '../LinkingController'
|
import { LinkingController } from '../LinkingController'
|
||||||
import { AbstractViewController } from '../Abstract/AbstractViewController'
|
import { AbstractViewController } from '../Abstract/AbstractViewController'
|
||||||
import { Persistable } from '../Abstract/Persistable'
|
|
||||||
import { log, LoggingDomain } from '@/Logging'
|
import { log, LoggingDomain } from '@/Logging'
|
||||||
|
|
||||||
const MinNoteCellHeight = 51.0
|
const MinNoteCellHeight = 51.0
|
||||||
@@ -55,14 +54,7 @@ enum ItemsReloadSource {
|
|||||||
FilterTextChange,
|
FilterTextChange,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ItemListControllerPersistableValue = {
|
export class ItemListController extends AbstractViewController implements InternalEventHandlerInterface {
|
||||||
displayOptions: DisplayOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ItemListController
|
|
||||||
extends AbstractViewController
|
|
||||||
implements Persistable<ItemListControllerPersistableValue>, InternalEventHandlerInterface
|
|
||||||
{
|
|
||||||
completedFullSync = false
|
completedFullSync = false
|
||||||
noteFilterText = ''
|
noteFilterText = ''
|
||||||
notes: SNNote[] = []
|
notes: SNNote[] = []
|
||||||
@@ -126,14 +118,6 @@ export class ItemListController
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
this.disposers.push(
|
|
||||||
reaction(
|
|
||||||
() => [this.navigationController.selected],
|
|
||||||
() => {
|
|
||||||
void this.reloadDisplayPreferences()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
this.disposers.push(
|
this.disposers.push(
|
||||||
application.streamItems<SNTag>([ContentType.Tag], async ({ changed, inserted }) => {
|
application.streamItems<SNTag>([ContentType.Tag], async ({ changed, inserted }) => {
|
||||||
const tags = [...changed, ...inserted]
|
const tags = [...changed, ...inserted]
|
||||||
@@ -142,10 +126,9 @@ export class ItemListController
|
|||||||
if (!didReloadItems) {
|
if (!didReloadItems) {
|
||||||
/** A tag could have changed its relationships, so we need to reload the filter */
|
/** A tag could have changed its relationships, so we need to reload the filter */
|
||||||
this.reloadNotesDisplayOptions()
|
this.reloadNotesDisplayOptions()
|
||||||
|
void this.reloadItems(ItemsReloadSource.ItemStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.reloadItems(ItemsReloadSource.ItemStream)
|
|
||||||
|
|
||||||
if (this.navigationController.selected && findInArray(tags, 'uuid', this.navigationController.selected.uuid)) {
|
if (this.navigationController.selected && findInArray(tags, 'uuid', this.navigationController.selected.uuid)) {
|
||||||
/** Tag title could have changed */
|
/** Tag title could have changed */
|
||||||
this.reloadPanelTitle()
|
this.reloadPanelTitle()
|
||||||
@@ -233,8 +216,6 @@ export class ItemListController
|
|||||||
|
|
||||||
optionsSubtitle: computed,
|
optionsSubtitle: computed,
|
||||||
activeControllerItem: computed,
|
activeControllerItem: computed,
|
||||||
|
|
||||||
hydrateFromPersistedValue: action,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
@@ -242,21 +223,6 @@ export class ItemListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPersistableValue = (): ItemListControllerPersistableValue => {
|
|
||||||
return {
|
|
||||||
displayOptions: this.displayOptions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hydrateFromPersistedValue = (state: ItemListControllerPersistableValue | undefined) => {
|
|
||||||
if (!state) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (state.displayOptions) {
|
|
||||||
this.displayOptions = state.displayOptions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||||
if (event.type === CrossControllerEvent.TagChanged) {
|
if (event.type === CrossControllerEvent.TagChanged) {
|
||||||
const payload = event.payload as { userTriggered: boolean }
|
const payload = event.payload as { userTriggered: boolean }
|
||||||
@@ -535,6 +501,7 @@ export class ItemListController
|
|||||||
}
|
}
|
||||||
newDisplayOptions.sortBy = sortBy
|
newDisplayOptions.sortBy = sortBy
|
||||||
|
|
||||||
|
const currentSortDirection = this.displayOptions.sortDirection
|
||||||
newDisplayOptions.sortDirection =
|
newDisplayOptions.sortDirection =
|
||||||
useBoolean(
|
useBoolean(
|
||||||
selectedTag?.preferences?.sortReverse,
|
selectedTag?.preferences?.sortReverse,
|
||||||
@@ -613,18 +580,14 @@ export class ItemListController
|
|||||||
|
|
||||||
await this.reloadItems(ItemsReloadSource.DisplayOptionsChange)
|
await this.reloadItems(ItemsReloadSource.DisplayOptionsChange)
|
||||||
|
|
||||||
if (
|
const didSortByChange = currentSortBy !== this.displayOptions.sortBy
|
||||||
newDisplayOptions.sortBy !== currentSortBy &&
|
const didSortDirectionChange = currentSortDirection !== this.displayOptions.sortDirection
|
||||||
this.shouldSelectFirstItem(ItemsReloadSource.DisplayOptionsChange)
|
const didSortPrefChange = didSortByChange || didSortDirectionChange
|
||||||
) {
|
|
||||||
|
if (didSortPrefChange && this.shouldSelectFirstItem(ItemsReloadSource.DisplayOptionsChange)) {
|
||||||
await this.selectFirstItem()
|
await this.selectFirstItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventBus.publish({
|
|
||||||
type: CrossControllerEvent.RequestValuePersistence,
|
|
||||||
payload: undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return { didReloadItems: true }
|
return { didReloadItems: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,9 +788,12 @@ export class ItemListController
|
|||||||
|
|
||||||
this.resetPagination()
|
this.resetPagination()
|
||||||
|
|
||||||
this.reloadNotesDisplayOptions()
|
const { didReloadItems } = await this.reloadDisplayPreferences()
|
||||||
|
|
||||||
await this.reloadItems(userTriggered ? ItemsReloadSource.UserTriggeredTagChange : ItemsReloadSource.TagChange)
|
if (!didReloadItems) {
|
||||||
|
this.reloadNotesDisplayOptions()
|
||||||
|
void this.reloadItems(userTriggered ? ItemsReloadSource.UserTriggeredTagChange : ItemsReloadSource.TagChange)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterEnter = () => {
|
onFilterEnter = () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { confirmDialog } from '@standardnotes/ui-services'
|
import { confirmDialog, NavigationControllerPersistableValue } from '@standardnotes/ui-services'
|
||||||
import { STRING_DELETE_TAG } from '@/Constants/Strings'
|
import { STRING_DELETE_TAG } from '@/Constants/Strings'
|
||||||
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER, SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants'
|
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER, SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants'
|
||||||
import {
|
import {
|
||||||
@@ -28,10 +28,6 @@ import { AbstractViewController } from '../Abstract/AbstractViewController'
|
|||||||
import { Persistable } from '../Abstract/Persistable'
|
import { Persistable } from '../Abstract/Persistable'
|
||||||
import { TagListSectionType } from '@/Components/Tags/TagListSection'
|
import { TagListSectionType } from '@/Components/Tags/TagListSection'
|
||||||
|
|
||||||
export type NavigationControllerPersistableValue = {
|
|
||||||
selectedTagUuid: AnyTag['uuid']
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NavigationController
|
export class NavigationController
|
||||||
extends AbstractViewController
|
extends AbstractViewController
|
||||||
implements Persistable<NavigationControllerPersistableValue>
|
implements Persistable<NavigationControllerPersistableValue>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
isFile,
|
isFile,
|
||||||
Uuids,
|
Uuids,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
|
import { SelectionControllerPersistableValue } from '@standardnotes/ui-services'
|
||||||
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
|
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
|
||||||
import { WebApplication } from '../Application/Application'
|
import { WebApplication } from '../Application/Application'
|
||||||
import { AbstractViewController } from './Abstract/AbstractViewController'
|
import { AbstractViewController } from './Abstract/AbstractViewController'
|
||||||
@@ -18,10 +19,6 @@ import { Persistable } from './Abstract/Persistable'
|
|||||||
import { CrossControllerEvent } from './CrossControllerEvent'
|
import { CrossControllerEvent } from './CrossControllerEvent'
|
||||||
import { ItemListController } from './ItemList/ItemListController'
|
import { ItemListController } from './ItemList/ItemListController'
|
||||||
|
|
||||||
export type SelectionControllerPersistableValue = {
|
|
||||||
selectedUuids: UuidString[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SelectedItemsController
|
export class SelectedItemsController
|
||||||
extends AbstractViewController
|
extends AbstractViewController
|
||||||
implements Persistable<SelectionControllerPersistableValue>
|
implements Persistable<SelectionControllerPersistableValue>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { PaneController } from './PaneController'
|
import { PaneController } from './PaneController'
|
||||||
import { storage, StorageKey, ToastService, ToastServiceInterface } from '@standardnotes/ui-services'
|
import {
|
||||||
|
PersistedStateValue,
|
||||||
|
PersistenceKey,
|
||||||
|
storage,
|
||||||
|
StorageKey,
|
||||||
|
ToastService,
|
||||||
|
ToastServiceInterface,
|
||||||
|
} from '@standardnotes/ui-services'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
import { destroyAllObjectProperties } from '@/Utils'
|
import { destroyAllObjectProperties } from '@/Utils'
|
||||||
@@ -18,7 +25,7 @@ import { ActionsMenuController } from './ActionsMenuController'
|
|||||||
import { FeaturesController } from './FeaturesController'
|
import { FeaturesController } from './FeaturesController'
|
||||||
import { FilesController } from './FilesController'
|
import { FilesController } from './FilesController'
|
||||||
import { NotesController } from './NotesController'
|
import { NotesController } from './NotesController'
|
||||||
import { ItemListController, ItemListControllerPersistableValue } from './ItemList/ItemListController'
|
import { ItemListController } from './ItemList/ItemListController'
|
||||||
import { NoAccountWarningController } from './NoAccountWarningController'
|
import { NoAccountWarningController } from './NoAccountWarningController'
|
||||||
import { PreferencesController } from './PreferencesController'
|
import { PreferencesController } from './PreferencesController'
|
||||||
import { PurchaseFlowController } from './PurchaseFlow/PurchaseFlowController'
|
import { PurchaseFlowController } from './PurchaseFlow/PurchaseFlowController'
|
||||||
@@ -26,12 +33,12 @@ import { QuickSettingsController } from './QuickSettingsController'
|
|||||||
import { SearchOptionsController } from './SearchOptionsController'
|
import { SearchOptionsController } from './SearchOptionsController'
|
||||||
import { SubscriptionController } from './Subscription/SubscriptionController'
|
import { SubscriptionController } from './Subscription/SubscriptionController'
|
||||||
import { SyncStatusController } from './SyncStatusController'
|
import { SyncStatusController } from './SyncStatusController'
|
||||||
import { NavigationController, NavigationControllerPersistableValue } from './Navigation/NavigationController'
|
import { NavigationController } from './Navigation/NavigationController'
|
||||||
import { FilePreviewModalController } from './FilePreviewModalController'
|
import { FilePreviewModalController } from './FilePreviewModalController'
|
||||||
import { SelectedItemsController, SelectionControllerPersistableValue } from './SelectedItemsController'
|
import { SelectedItemsController } from './SelectedItemsController'
|
||||||
import { HistoryModalController } from './NoteHistory/HistoryModalController'
|
import { HistoryModalController } from './NoteHistory/HistoryModalController'
|
||||||
import { LinkingController } from './LinkingController'
|
import { LinkingController } from './LinkingController'
|
||||||
import { MasterPersistedValue, PersistenceKey, PersistenceService } from './Abstract/PersistenceService'
|
import { PersistenceService } from './Abstract/PersistenceService'
|
||||||
import { CrossControllerEvent } from './CrossControllerEvent'
|
import { CrossControllerEvent } from './CrossControllerEvent'
|
||||||
import { EventObserverInterface } from '@/Event/EventObserverInterface'
|
import { EventObserverInterface } from '@/Event/EventObserverInterface'
|
||||||
import { ApplicationEventObserver } from '@/Event/ApplicationEventObserver'
|
import { ApplicationEventObserver } from '@/Event/ApplicationEventObserver'
|
||||||
@@ -263,29 +270,40 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
persistValues = (): void => {
|
persistValues = (): void => {
|
||||||
const values: MasterPersistedValue = {
|
const values: PersistedStateValue = {
|
||||||
[PersistenceKey.SelectedItemsController]: this.selectionController.getPersistableValue(),
|
[PersistenceKey.SelectedItemsController]: this.selectionController.getPersistableValue(),
|
||||||
[PersistenceKey.NavigationController]: this.navigationController.getPersistableValue(),
|
[PersistenceKey.NavigationController]: this.navigationController.getPersistableValue(),
|
||||||
[PersistenceKey.ItemListController]: this.itemListController.getPersistableValue(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.persistenceService.persistValues(values)
|
this.persistenceService.persistValues(values)
|
||||||
|
|
||||||
|
const selectedItemsState = values['selected-items-controller']
|
||||||
|
const navigationSelectionState = values['navigation-controller']
|
||||||
|
const launchPriorityUuids: string[] = []
|
||||||
|
if (selectedItemsState.selectedUuids.length) {
|
||||||
|
launchPriorityUuids.push(...selectedItemsState.selectedUuids)
|
||||||
|
}
|
||||||
|
if (navigationSelectionState.selectedTagUuid) {
|
||||||
|
launchPriorityUuids.push(navigationSelectionState.selectedTagUuid)
|
||||||
|
}
|
||||||
|
this.application.sync.setLaunchPriorityUuids(launchPriorityUuids)
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrateFromPersistedValues = (values: MasterPersistedValue | undefined): void => {
|
clearPersistedValues = (): void => {
|
||||||
const itemListState = values?.[PersistenceKey.ItemListController] as ItemListControllerPersistableValue
|
this.persistenceService.clearPersistedValues()
|
||||||
this.itemListController.hydrateFromPersistedValue(itemListState)
|
}
|
||||||
|
|
||||||
const selectedItemsState = values?.[PersistenceKey.SelectedItemsController] as SelectionControllerPersistableValue
|
hydrateFromPersistedValues = (values: PersistedStateValue | undefined): void => {
|
||||||
this.selectionController.hydrateFromPersistedValue(selectedItemsState)
|
const navigationState = values?.[PersistenceKey.NavigationController]
|
||||||
|
|
||||||
const navigationState = values?.[PersistenceKey.NavigationController] as NavigationControllerPersistableValue
|
|
||||||
this.navigationController.hydrateFromPersistedValue(navigationState)
|
this.navigationController.hydrateFromPersistedValue(navigationState)
|
||||||
|
|
||||||
|
const selectedItemsState = values?.[PersistenceKey.SelectedItemsController]
|
||||||
|
this.selectionController.hydrateFromPersistedValue(selectedItemsState)
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||||
if (event.type === CrossControllerEvent.HydrateFromPersistedValues) {
|
if (event.type === CrossControllerEvent.HydrateFromPersistedValues) {
|
||||||
this.hydrateFromPersistedValues(event.payload as MasterPersistedValue | undefined)
|
this.hydrateFromPersistedValues(event.payload as PersistedStateValue | undefined)
|
||||||
} else if (event.type === CrossControllerEvent.RequestValuePersistence) {
|
} else if (event.type === CrossControllerEvent.RequestValuePersistence) {
|
||||||
this.persistValues()
|
this.persistValues()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user