feat(web): extract ui-services package

This commit is contained in:
Karol Sójko
2022-08-04 15:13:30 +02:00
parent c72a407095
commit 7e251262d7
161 changed files with 1105 additions and 824 deletions

View File

@@ -0,0 +1,7 @@
import { ApplicationInterface } from './ApplicationInterface'
import { DeinitCallback } from './DeinitCallback'
export interface AppGroupManagedApplication extends ApplicationInterface {
onDeinit: DeinitCallback
setOnDeinit(onDeinit: DeinitCallback): void
}

View File

@@ -1,22 +1,44 @@
import { ApplicationIdentifier } from '@standardnotes/common'
import { ApplicationIdentifier, ContentType } from '@standardnotes/common'
import { BackupFile, DecryptedItemInterface, ItemStream, PrefKey, PrefValue } from '@standardnotes/models'
import { ComponentManagerInterface } from '../Component/ComponentManagerInterface'
import { ApplicationEvent } from '../Event/ApplicationEvent'
import { ApplicationEventCallback } from '../Event/ApplicationEventCallback'
import { FeaturesClientInterface } from '../Feature/FeaturesClientInterface'
import { ItemsClientInterface } from '../Item/ItemsClientInterface'
import { MutatorClientInterface } from '../Mutator/MutatorClientInterface'
import { StorageValueModes } from '../Storage/StorageTypes'
import { DeinitCallback } from './DeinitCallback'
import { DeinitMode } from './DeinitMode'
import { DeinitSource } from './DeinitSource'
import { UserClientInterface } from './UserClientInterface'
export interface ApplicationInterface {
deinit(mode: DeinitMode, source: DeinitSource): void
getDeinitMode(): DeinitMode
isStarted(): boolean
isLaunched(): boolean
addEventObserver(callback: ApplicationEventCallback, singleEvent?: ApplicationEvent): () => void
hasProtectionSources(): boolean
createEncryptedBackupFileForAutomatedDesktopBackups(): Promise<BackupFile | undefined>
createDecryptedBackupFile(): Promise<BackupFile | undefined>
hasPasscode(): boolean
lock(): Promise<void>
setValue(key: string, value: unknown, mode?: StorageValueModes): void
getValue(key: string, mode?: StorageValueModes): unknown
removeValue(key: string, mode?: StorageValueModes): Promise<void>
isLocked(): Promise<boolean>
getPreference<K extends PrefKey>(key: K): PrefValue[K] | undefined
getPreference<K extends PrefKey>(key: K, defaultValue: PrefValue[K]): PrefValue[K]
getPreference<K extends PrefKey>(key: K, defaultValue?: PrefValue[K]): PrefValue[K] | undefined
streamItems<I extends DecryptedItemInterface = DecryptedItemInterface>(
contentType: ContentType | ContentType[],
stream: ItemStream<I>,
): () => void
get features(): FeaturesClientInterface
get componentManager(): ComponentManagerInterface
get items(): ItemsClientInterface
get mutator(): MutatorClientInterface
get user(): UserClientInterface
readonly identifier: ApplicationIdentifier
}
export interface AppGroupManagedApplication extends ApplicationInterface {
onDeinit: DeinitCallback
setOnDeinit(onDeinit: DeinitCallback): void
}

View File

@@ -1,5 +1,5 @@
import { DeinitSource } from './DeinitSource'
import { DeinitMode } from './DeinitMode'
import { AppGroupManagedApplication } from './ApplicationInterface'
import { AppGroupManagedApplication } from './AppGroupManagedApplication'
export type DeinitCallback = (application: AppGroupManagedApplication, mode: DeinitMode, source: DeinitSource) => void

View File

@@ -0,0 +1,8 @@
import { DesktopManagerInterface } from '../Device/DesktopManagerInterface'
import { WebAppEvent } from '../Event/WebAppEvent'
import { ApplicationInterface } from './ApplicationInterface'
export interface WebApplicationInterface extends ApplicationInterface {
notifyWebEvent(event: WebAppEvent, data?: unknown): void
getDesktopService(): DesktopManagerInterface | undefined
}

View File

@@ -0,0 +1,23 @@
import { Uuid } from '@standardnotes/common'
import { ComponentArea } from '@standardnotes/features'
import { ActionObserver, PermissionDialog, SNComponent, SNNote } from '@standardnotes/models'
import { DesktopManagerInterface } from '../Device/DesktopManagerInterface'
import { ComponentViewerInterface } from './ComponentViewerInterface'
export interface ComponentManagerInterface {
urlForComponent(component: SNComponent): string | undefined
setDesktopManager(desktopManager: DesktopManagerInterface): void
componentsForArea(area: ComponentArea): SNComponent[]
editorForNote(note: SNNote): SNComponent | undefined
doesEditorChangeRequireAlert(from: SNComponent | undefined, to: SNComponent | undefined): boolean
showEditorChangeAlert(): Promise<boolean>
destroyComponentViewer(viewer: ComponentViewerInterface): void
createComponentViewer(
component: SNComponent,
contextItem?: Uuid,
actionObserver?: ActionObserver,
urlOverride?: string,
): ComponentViewerInterface
presentPermissionsDialog(_dialog: PermissionDialog): void
}

View File

@@ -0,0 +1,4 @@
export enum ComponentViewerError {
OfflineRestricted = 'OfflineRestricted',
MissingUrl = 'MissingUrl',
}

View File

@@ -0,0 +1,29 @@
import {
ActionObserver,
ComponentEventObserver,
ComponentMessage,
DecryptedItemInterface,
SNComponent,
} from '@standardnotes/models'
import { FeatureStatus } from '../Feature/FeatureStatus'
import { ComponentViewerError } from './ComponentViewerError'
export interface ComponentViewerInterface {
readonly component: SNComponent
readonly url?: string
identifier: string
lockReadonly: boolean
sessionKey?: string
overrideContextItem?: DecryptedItemInterface
get componentUuid(): string
destroy(): void
setReadonly(readonly: boolean): void
getFeatureStatus(): FeatureStatus
shouldRender(): boolean
getError(): ComponentViewerError | undefined
setWindow(window: Window): void
addEventObserver(observer: ComponentEventObserver): () => void
addActionObserver(observer: ActionObserver): () => void
postActiveThemes(): void
handleMessage(message: ComponentMessage): void
}

View File

@@ -0,0 +1,7 @@
import { SNComponent } from '@standardnotes/models'
export interface DesktopManagerInterface {
syncComponentsInstallation(components: SNComponent[]): void
registerUpdateObserver(callback: (component: SNComponent) => void): () => void
getExtServerHost(): string
}

View File

@@ -0,0 +1,65 @@
export enum ApplicationEvent {
SignedIn = 2,
SignedOut = 3,
/** When a full, potentially multi-page sync completes */
CompletedFullSync = 5,
FailedSync = 6,
HighLatencySync = 7,
EnteredOutOfSync = 8,
ExitedOutOfSync = 9,
/**
* The application has finished it `prepareForLaunch` state and is now ready for unlock
* Called when the application has initialized and is ready for launch, but before
* the application has been unlocked, if applicable. Use this to do pre-launch
* configuration, but do not attempt to access user data like notes or tags.
*/
Started = 10,
/**
* The applicaiton is fully unlocked and ready for i/o
* Called when the application has been fully decrypted and unlocked. Use this to
* to begin streaming data like notes and tags.
*/
Launched = 11,
LocalDataLoaded = 12,
/**
* When the root key or root key wrapper changes. Includes events like account state
* changes (registering, signing in, changing pw, logging out) and passcode state
* changes (adding, removing, changing).
*/
KeyStatusChanged = 13,
MajorDataChange = 14,
CompletedRestart = 15,
LocalDataIncrementalLoad = 16,
SyncStatusChanged = 17,
WillSync = 18,
InvalidSyncSession = 19,
LocalDatabaseReadError = 20,
LocalDatabaseWriteError = 21,
/** When a single roundtrip completes with sync, in a potentially multi-page sync request.
* If just a single roundtrip, this event will be triggered, along with CompletedFullSync */
CompletedIncrementalSync = 22,
/**
* The application has loaded all pending migrations (but not run any, except for the base one),
* and consumers may now call `hasPendingMigrations`
*/
MigrationsLoaded = 23,
/** When StorageService is ready to start servicing read/write requests */
StorageReady = 24,
PreferencesChanged = 25,
UnprotectedSessionBegan = 26,
UserRolesChanged = 27,
FeaturesUpdated = 28,
UnprotectedSessionExpired = 29,
/** Called when the app first launches and after first sync request made after sign in */
CompletedInitialSync = 30,
}

View File

@@ -0,0 +1,3 @@
import { ApplicationEvent } from './ApplicationEvent'
export type ApplicationEventCallback = (event: ApplicationEvent, data?: unknown) => Promise<void>

View File

@@ -0,0 +1,9 @@
export enum WebAppEvent {
NewUpdateAvailable = 'NewUpdateAvailable',
EditorFocused = 'EditorFocused',
BeganBackupDownload = 'BeganBackupDownload',
EndedBackupDownload = 'EndedBackupDownload',
PanelResized = 'PanelResized',
WindowDidFocus = 'WindowDidFocus',
WindowDidBlur = 'WindowDidBlur',
}

View File

@@ -0,0 +1,6 @@
export enum FeatureStatus {
NoUserSubscription = 'NoUserSubscription',
NotInCurrentPlan = 'NotInCurrentPlan',
InCurrentPlanButExpired = 'InCurrentPlanButExpired',
Entitled = 'Entitled',
}

View File

@@ -0,0 +1,38 @@
import { FeatureDescription, FeatureIdentifier } from '@standardnotes/features'
import { SNComponent } from '@standardnotes/models'
import { RoleName } from '@standardnotes/common'
import { FeatureStatus } from './FeatureStatus'
import { SetOfflineFeaturesFunctionResponse } from './SetOfflineFeaturesFunctionResponse'
export interface FeaturesClientInterface {
downloadExternalFeature(urlOrCode: string): Promise<SNComponent | undefined>
getUserFeature(featureId: FeatureIdentifier): FeatureDescription | undefined
getFeatureStatus(featureId: FeatureIdentifier): FeatureStatus
hasMinimumRole(role: RoleName): boolean
setOfflineFeaturesCode(code: string): Promise<SetOfflineFeaturesFunctionResponse>
hasOfflineRepo(): boolean
deleteOfflineFeatureRepo(): Promise<void>
isThirdPartyFeature(identifier: string): boolean
toggleExperimentalFeature(identifier: FeatureIdentifier): void
getExperimentalFeatures(): FeatureIdentifier[]
getEnabledExperimentalFeatures(): FeatureIdentifier[]
enableExperimentalFeature(identifier: FeatureIdentifier): void
disableExperimentalFeature(identifier: FeatureIdentifier): void
isExperimentalFeatureEnabled(identifier: FeatureIdentifier): boolean
isExperimentalFeature(identifier: FeatureIdentifier): boolean
}

View File

@@ -0,0 +1,4 @@
export enum FeaturesEvent {
UserRolesChanged = 'UserRolesChanged',
FeaturesUpdated = 'FeaturesUpdated',
}

View File

@@ -0,0 +1,4 @@
export type OfflineSubscriptionEntitlements = {
featuresUrl: string
extensionKey: string
}

View File

@@ -0,0 +1,3 @@
import { ClientDisplayableError } from '@standardnotes/responses'
export type SetOfflineFeaturesFunctionResponse = ClientDisplayableError | undefined

View File

@@ -0,0 +1,140 @@
import { ContentType, Uuid } from '@standardnotes/common'
import {
SNNote,
FileItem,
SNTag,
SmartView,
TagNoteCountChangeObserver,
DecryptedPayloadInterface,
EncryptedItemInterface,
DecryptedTransferPayload,
PredicateInterface,
DecryptedItemInterface,
SNComponent,
SNTheme,
DisplayOptions,
ItemsKeyInterface,
} from '@standardnotes/models'
export interface ItemsClientInterface {
get invalidItems(): EncryptedItemInterface[]
associateFileWithNote(file: FileItem, note: SNNote): Promise<FileItem>
disassociateFileWithNote(file: FileItem, note: SNNote): Promise<FileItem>
getFilesForNote(note: SNNote): FileItem[]
renameFile(file: FileItem, name: string): Promise<FileItem>
addTagToNote(note: SNNote, tag: SNTag, addHierarchy: boolean): Promise<SNTag[]>
/** Creates an unmanaged, un-inserted item from a payload. */
createItemFromPayload(payload: DecryptedPayloadInterface): DecryptedItemInterface
createPayloadFromObject(object: DecryptedTransferPayload): DecryptedPayloadInterface
get trashedItems(): SNNote[]
setPrimaryItemDisplayOptions(options: DisplayOptions): void
getDisplayableNotes(): SNNote[]
getDisplayableTags(): SNTag[]
getDisplayableItemsKeys(): ItemsKeyInterface[]
getDisplayableFiles(): FileItem[]
getDisplayableNotesAndFiles(): (SNNote | FileItem)[]
getDisplayableComponents(): (SNComponent | SNTheme)[]
getItems<T extends DecryptedItemInterface>(contentType: ContentType | ContentType[]): T[]
notesMatchingSmartView(view: SmartView): SNNote[]
addNoteCountChangeObserver(observer: TagNoteCountChangeObserver): () => void
allCountableNotesCount(): number
countableNotesForTag(tag: SNTag | SmartView): number
findTagByTitle(title: string): SNTag | undefined
getTagPrefixTitle(tag: SNTag): string | undefined
getTagLongTitle(tag: SNTag): string
hasTagsNeedingFoldersMigration(): boolean
referencesForItem(itemToLookupUuidFor: DecryptedItemInterface, contentType?: ContentType): DecryptedItemInterface[]
itemsReferencingItem(itemToLookupUuidFor: DecryptedItemInterface, contentType?: ContentType): DecryptedItemInterface[]
/**
* Finds tags with title or component starting with a search query and (optionally) not associated with a note
* @param searchQuery - The query string to match
* @param note - The note whose tags should be omitted from results
* @returns Array containing tags matching search query and not associated with note
*/
searchTags(searchQuery: string, note?: SNNote): SNTag[]
isValidTagParent(parentTagToLookUpUuidFor: SNTag, childToLookUpUuidFor: SNTag): boolean
/**
* Returns the parent for a tag
*/
getTagParent(itemToLookupUuidFor: SNTag): SNTag | undefined
/**
* Returns the hierarchy of parents for a tag
* @returns Array containing all parent tags
*/
getTagParentChain(itemToLookupUuidFor: SNTag): SNTag[]
/**
* Returns all descendants for a tag
* @returns Array containing all descendant tags
*/
getTagChildren(itemToLookupUuidFor: SNTag): SNTag[]
/**
* Get tags for a note sorted in natural order
* @param note - The note whose tags will be returned
* @returns Array containing tags associated with a note
*/
getSortedTagsForNote(note: SNNote): SNTag[]
isSmartViewTitle(title: string): boolean
getSmartViews(): SmartView[]
getNoteCount(): number
/**
* Finds an item by UUID.
*/
findItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: Uuid): T | undefined
/**
* Finds an item by predicate.
*/
findItems<T extends DecryptedItemInterface>(uuids: Uuid[]): T[]
findSureItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: Uuid): T
/**
* Finds an item by predicate.
*/
itemsMatchingPredicate<T extends DecryptedItemInterface>(
contentType: ContentType,
predicate: PredicateInterface<T>,
): T[]
/**
* @param item item to be checked
* @returns Whether the item is a template (unmanaged)
*/
isTemplateItem(item: DecryptedItemInterface): boolean
}

View File

@@ -0,0 +1,184 @@
import { ContentType } from '@standardnotes/common'
import {
BackupFile,
DecryptedItemInterface,
DecryptedItemMutator,
EncryptedItemInterface,
FileItem,
ItemContent,
PayloadEmitSource,
SmartView,
SNComponent,
SNNote,
SNTag,
TransactionalMutation,
} from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { ChallengeReason } from '../Challenge/Types/ChallengeReason'
import { SyncOptions } from '../Sync/SyncOptions'
export interface MutatorClientInterface {
/**
* Inserts the input item by its payload properties, and marks the item as dirty.
* A sync is not performed after an item is inserted. This must be handled by the caller.
*/
insertItem(item: DecryptedItemInterface): Promise<DecryptedItemInterface>
/**
* Mutates a pre-existing item, marks it as dirty, and syncs it
*/
changeAndSaveItem<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemToLookupUuidFor: DecryptedItemInterface,
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
emitSource?: PayloadEmitSource,
syncOptions?: SyncOptions,
): Promise<DecryptedItemInterface | undefined>
/**
* Mutates pre-existing items, marks them as dirty, and syncs
*/
changeAndSaveItems<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemsToLookupUuidsFor: DecryptedItemInterface[],
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
emitSource?: PayloadEmitSource,
syncOptions?: SyncOptions,
): Promise<void>
/**
* Mutates a pre-existing item and marks it as dirty. Does not sync changes.
*/
changeItem<M extends DecryptedItemMutator>(
itemToLookupUuidFor: DecryptedItemInterface,
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
): Promise<DecryptedItemInterface | undefined>
/**
* Mutates a pre-existing items and marks them as dirty. Does not sync changes.
*/
changeItems<M extends DecryptedItemMutator = DecryptedItemMutator>(
itemsToLookupUuidsFor: DecryptedItemInterface[],
mutate: (mutator: M) => void,
updateTimestamps?: boolean,
): Promise<(DecryptedItemInterface | undefined)[]>
/**
* Run unique mutations per each item in the array, then only propagate all changes
* once all mutations have been run. This differs from `changeItems` in that changeItems
* runs the same mutation on all items.
*/
runTransactionalMutations(
transactions: TransactionalMutation[],
emitSource?: PayloadEmitSource,
payloadSourceKey?: string,
): Promise<(DecryptedItemInterface | undefined)[]>
runTransactionalMutation(
transaction: TransactionalMutation,
emitSource?: PayloadEmitSource,
payloadSourceKey?: string,
): Promise<DecryptedItemInterface | undefined>
protectItems<_M extends DecryptedItemMutator<ItemContent>, I extends DecryptedItemInterface<ItemContent>>(
items: I[],
): Promise<I[]>
unprotectItems<_M extends DecryptedItemMutator<ItemContent>, I extends DecryptedItemInterface<ItemContent>>(
items: I[],
reason: ChallengeReason,
): Promise<I[] | undefined>
protectNote(note: SNNote): Promise<SNNote>
unprotectNote(note: SNNote): Promise<SNNote | undefined>
protectNotes(notes: SNNote[]): Promise<SNNote[]>
unprotectNotes(notes: SNNote[]): Promise<SNNote[]>
protectFile(file: FileItem): Promise<FileItem>
unprotectFile(file: FileItem): Promise<FileItem | undefined>
/**
* Takes the values of the input item and emits it onto global state.
*/
mergeItem(item: DecryptedItemInterface, source: PayloadEmitSource): Promise<DecryptedItemInterface>
/**
* Creates an unmanaged item that can be added later.
*/
createTemplateItem<
C extends ItemContent = ItemContent,
I extends DecryptedItemInterface<C> = DecryptedItemInterface<C>,
>(
contentType: ContentType,
content?: C,
): I
/**
* @param isUserModified Whether to change the modified date the user
* sees of the item.
*/
setItemNeedsSync(item: DecryptedItemInterface, isUserModified?: boolean): Promise<DecryptedItemInterface | undefined>
setItemsNeedsSync(items: DecryptedItemInterface[]): Promise<(DecryptedItemInterface | undefined)[]>
deleteItem(item: DecryptedItemInterface | EncryptedItemInterface): Promise<void>
deleteItems(items: (DecryptedItemInterface | EncryptedItemInterface)[]): Promise<void>
emptyTrash(): Promise<void>
duplicateItem<T extends DecryptedItemInterface>(item: T, additionalContent?: Partial<T['content']>): Promise<T>
/**
* Migrates any tags containing a '.' character to sa chema-based heirarchy, removing
* the dot from the tag's title.
*/
migrateTagsToFolders(): Promise<unknown>
/**
* Establishes a hierarchical relationship between two tags.
*/
setTagParent(parentTag: SNTag, childTag: SNTag): Promise<void>
/**
* Remove the tag parent.
*/
unsetTagParent(childTag: SNTag): Promise<void>
findOrCreateTag(title: string): Promise<SNTag>
/** Creates and returns the tag but does not run sync. Callers must perform sync. */
createTagOrSmartView(title: string): Promise<SNTag | SmartView>
/**
* Activates or deactivates a component, depending on its
* current state, and syncs.
*/
toggleComponent(component: SNComponent): Promise<void>
toggleTheme(theme: SNComponent): Promise<void>
/**
* @returns
* .affectedItems: Items that were either created or dirtied by this import
* .errorCount: The number of items that were not imported due to failure to decrypt.
*/
importData(
data: BackupFile,
awaitSync?: boolean,
): Promise<
| {
affectedItems: DecryptedItemInterface[]
errorCount: number
}
| {
error: ClientDisplayableError
}
>
}

View File

@@ -1,13 +1,19 @@
export * from './Alert/AlertService'
export * from './Api/ApiServiceInterface'
export * from './Application/AppGroupManagedApplication'
export * from './Application/ApplicationInterface'
export * from './Application/ApplicationStage'
export * from './Application/DeinitCallback'
export * from './Application/DeinitSource'
export * from './Application/DeinitMode'
export * from './Application/UserClientInterface'
export * from './Application/ApplicationInterface'
export * from './Application/WebApplicationInterface'
export * from './Challenge'
export * from './Component/ComponentManagerInterface'
export * from './Component/ComponentViewerError'
export * from './Component/ComponentViewerInterface'
export * from './Device/DesktopDeviceInterface'
export * from './Device/DesktopManagerInterface'
export * from './Device/DesktopWebCommunication'
export * from './Device/DeviceInterface'
export * from './Device/Environments'
@@ -16,9 +22,17 @@ export * from './Device/MobileDeviceInterface'
export * from './Device/TypeCheck'
export * from './Device/WebOrDesktopDeviceInterface'
export * from './Diagnostics/ServiceDiagnostics'
export * from './Event/ApplicationEvent'
export * from './Event/ApplicationEventCallback'
export * from './Event/EventObserver'
export * from './Event/SyncEvent'
export * from './Event/SyncEventReceiver'
export * from './Event/WebAppEvent'
export * from './Feature/FeatureStatus'
export * from './Feature/FeaturesClientInterface'
export * from './Feature/FeaturesEvent'
export * from './Feature/OfflineSubscriptionEntitlements'
export * from './Feature/SetOfflineFeaturesFunctionResponse'
export * from './Files/FilesApiInterface'
export * from './FileSystem/FileSystemApi'
export * from './Integrity/IntegrityApiInterface'
@@ -32,7 +46,9 @@ export * from './Internal/InternalEventInterface'
export * from './Internal/InternalEventPublishStrategy'
export * from './Internal/InternalEventType'
export * from './Item/ItemManagerInterface'
export * from './Item/ItemsClientInterface'
export * from './Item/ItemsServerInterface'
export * from './Mutator/MutatorClientInterface'
export * from './Payloads/PayloadManagerInterface'
export * from './Preferences/PreferenceServiceInterface'
export * from './Service/AbstractService'