refactor: merge themes into components (#2388)
This commit is contained in:
@@ -12,10 +12,10 @@ import {
|
||||
isThemeFeatureDescription,
|
||||
} from '@standardnotes/features'
|
||||
import { ComponentInterface } from '../../Syncable/Component/ComponentInterface'
|
||||
import { isTheme } from '../../Syncable/Theme'
|
||||
import { isItemBasedFeature, isNativeFeature } from './TypeGuards'
|
||||
import { UIFeatureInterface } from './UIFeatureInterface'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { ThemePackageInfo, isTheme } from '../../Syncable/Component'
|
||||
|
||||
export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeatureInterface<F> {
|
||||
constructor(public readonly item: ComponentInterface | F) {}
|
||||
@@ -40,6 +40,14 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
||||
throw new Error('Cannot cast item to component')
|
||||
}
|
||||
|
||||
get asTheme(): ComponentInterface<ThemePackageInfo> {
|
||||
if (isItemBasedFeature(this.item)) {
|
||||
return this.item
|
||||
}
|
||||
|
||||
throw new Error('Cannot cast item to component')
|
||||
}
|
||||
|
||||
get asFeatureDescription(): F {
|
||||
if (isNativeFeature(this.item)) {
|
||||
return this.item
|
||||
@@ -145,7 +153,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
||||
|
||||
get layerable(): boolean {
|
||||
if (isItemBasedFeature(this.item) && isTheme(this.item)) {
|
||||
return this.item.layerable
|
||||
return this.item.layerableTheme
|
||||
} else if (isThemeFeatureDescription(this.asFeatureDescription)) {
|
||||
return this.asFeatureDescription.layerable ?? false
|
||||
}
|
||||
@@ -155,7 +163,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
||||
|
||||
get dockIcon(): ThemeDockIcon | undefined {
|
||||
if (isItemBasedFeature(this.item) && isTheme(this.item)) {
|
||||
return this.item.package_info.dock_icon
|
||||
return this.asTheme.package_info.dock_icon
|
||||
} else if (isThemeFeatureDescription(this.asFeatureDescription)) {
|
||||
return this.asFeatureDescription.dock_icon
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import { PayloadSource } from './../../Abstract/Payload/Types/PayloadSource'
|
||||
import { DecryptedPayload } from './../../Abstract/Payload/Implementations/DecryptedPayload'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { FillItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { SNComponent } from './Component'
|
||||
import { ComponentItem } from './Component'
|
||||
import { ComponentContent } from './ComponentContent'
|
||||
import { PayloadTimestampDefaults } from '../../Abstract/Payload'
|
||||
import { NoteType } from '@standardnotes/features'
|
||||
|
||||
describe('component model', () => {
|
||||
it('valid hosted url should ignore url', () => {
|
||||
const component = new SNComponent(
|
||||
const component = new ComponentItem(
|
||||
new DecryptedPayload(
|
||||
{
|
||||
uuid: String(Math.random()),
|
||||
@@ -29,7 +29,7 @@ describe('component model', () => {
|
||||
})
|
||||
|
||||
it('invalid hosted url should fallback to url', () => {
|
||||
const component = new SNComponent(
|
||||
const component = new ComponentItem(
|
||||
new DecryptedPayload(
|
||||
{
|
||||
uuid: String(Math.random()),
|
||||
@@ -49,7 +49,7 @@ describe('component model', () => {
|
||||
})
|
||||
|
||||
it('should return noteType as specified in package_info', () => {
|
||||
const component = new SNComponent(
|
||||
const component = new ComponentItem(
|
||||
new DecryptedPayload(
|
||||
{
|
||||
uuid: String(Math.random()),
|
||||
@@ -69,7 +69,7 @@ describe('component model', () => {
|
||||
})
|
||||
|
||||
it('should return unknown as noteType if no note type defined in package_info', () => {
|
||||
const component = new SNComponent(
|
||||
const component = new ComponentItem(
|
||||
new DecryptedPayload(
|
||||
{
|
||||
uuid: String(Math.random()),
|
||||
|
||||
@@ -17,34 +17,16 @@ import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/Dec
|
||||
import { HistoryEntryInterface } from '../../Runtime/History'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { Predicate } from '../../Runtime/Predicate/Predicate'
|
||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||
import { DecryptedItemInterface } from './../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { ComponentPackageInfo } from './PackageInfo'
|
||||
import { isDecryptedItem } from '../../Abstract/Item'
|
||||
import { ComponentPackageInfo, ThemePackageInfo } from './PackageInfo'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
export function isComponent(x: ItemInterface): x is ComponentInterface {
|
||||
if (!isDecryptedItem(x as DecryptedItemInterface)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return x.content_type === ContentType.TYPES.Component
|
||||
}
|
||||
|
||||
export function isComponentOrTheme(x: ItemInterface): x is ComponentInterface {
|
||||
if (!isDecryptedItem(x as DecryptedItemInterface)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return x.content_type === ContentType.TYPES.Component || x.content_type === ContentType.TYPES.Theme
|
||||
}
|
||||
|
||||
/**
|
||||
* Components are mostly iframe based extensions that communicate with the SN parent
|
||||
* via the postMessage API. However, a theme can also be a component, which is activated
|
||||
* only by its url.
|
||||
*/
|
||||
export class SNComponent extends DecryptedItem<ComponentContent> implements ComponentInterface {
|
||||
export class ComponentItem extends DecryptedItem<ComponentContent> implements ComponentInterface {
|
||||
public readonly legacyComponentData: Record<string, unknown>
|
||||
/** Items that have requested a component to be disabled in its context */
|
||||
public readonly disassociatedItemIds: string[]
|
||||
@@ -61,7 +43,6 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
public readonly valid_until: Date
|
||||
public readonly legacyActive: boolean
|
||||
public readonly legacy_url?: string
|
||||
public readonly isMobileDefault: boolean
|
||||
|
||||
constructor(payload: DecryptedPayloadInterface<ComponentContent>) {
|
||||
super(payload)
|
||||
@@ -78,13 +59,18 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
this.valid_until = new Date(payload.content.valid_until || 0)
|
||||
this.offlineOnly = payload.content.offlineOnly ?? false
|
||||
this.name = payload.content.name
|
||||
this.area = payload.content.area
|
||||
|
||||
if (this.content_type === ContentType.TYPES.Theme) {
|
||||
this.area = ComponentArea.Themes
|
||||
} else {
|
||||
this.area = payload.content.area
|
||||
}
|
||||
|
||||
this.package_info = payload.content.package_info || {}
|
||||
this.permissions = payload.content.permissions || []
|
||||
this.autoupdateDisabled = payload.content.autoupdateDisabled ?? false
|
||||
this.disassociatedItemIds = payload.content.disassociatedItemIds || []
|
||||
this.associatedItemIds = payload.content.associatedItemIds || []
|
||||
this.isMobileDefault = payload.content.isMobileDefault ?? false
|
||||
|
||||
/**
|
||||
* @legacy
|
||||
@@ -116,15 +102,11 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
return FindNativeFeature(this.identifier)?.name || this.name
|
||||
}
|
||||
|
||||
public override singletonPredicate(): Predicate<SNComponent> {
|
||||
const uniqueIdentifierPredicate = new Predicate<SNComponent>('identifier', '=', this.identifier)
|
||||
public override singletonPredicate(): Predicate<ComponentItem> {
|
||||
const uniqueIdentifierPredicate = new Predicate<ComponentItem>('identifier', '=', this.identifier)
|
||||
return uniqueIdentifierPredicate
|
||||
}
|
||||
|
||||
public isEditor(): boolean {
|
||||
return this.area === ComponentArea.Editor
|
||||
}
|
||||
|
||||
public isTheme(): boolean {
|
||||
return this.content_type === ContentType.TYPES.Theme || this.area === ComponentArea.Themes
|
||||
}
|
||||
@@ -134,10 +116,6 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
return this.getAppDomainValue(AppDataField.DefaultEditor) === true
|
||||
}
|
||||
|
||||
public getLastSize(): unknown {
|
||||
return this.getAppDomainValue(AppDataField.LastSize)
|
||||
}
|
||||
|
||||
public hasValidHostedUrl(): boolean {
|
||||
return (this.hosted_url || this.legacy_url) != undefined
|
||||
}
|
||||
@@ -149,19 +127,6 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
return [...componentKeys, ...superKeys] as (keyof ItemContent)[]
|
||||
}
|
||||
|
||||
/**
|
||||
* An associative component depends on being explicitly activated for a
|
||||
* given item, compared to a dissaciative component, which is enabled by
|
||||
* default in areas unrelated to a certain item.
|
||||
*/
|
||||
public static associativeAreas(): ComponentArea[] {
|
||||
return [ComponentArea.Editor]
|
||||
}
|
||||
|
||||
public isAssociative(): boolean {
|
||||
return SNComponent.associativeAreas().includes(this.area)
|
||||
}
|
||||
|
||||
public isExplicitlyEnabledForItem(uuid: string): boolean {
|
||||
return this.associatedItemIds.indexOf(uuid) !== -1
|
||||
}
|
||||
@@ -199,4 +164,13 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
public get deprecationMessage(): string | undefined {
|
||||
return this.package_info.deprecation_message
|
||||
}
|
||||
|
||||
get layerableTheme(): boolean {
|
||||
if (!this.isTheme()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const themePackageInfo = this.package_info as ThemePackageInfo
|
||||
return themePackageInfo?.layerable ?? false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export type ComponentContentSpecialized = {
|
||||
valid_until: Date | number
|
||||
|
||||
legacy_url?: string
|
||||
isMobileDefault?: boolean
|
||||
isDeprecated?: boolean
|
||||
|
||||
/** @deprecated */
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ComponentArea, ComponentPermission, NoteType, ThirdPartyFeatureDescription } from '@standardnotes/features'
|
||||
import { ComponentPackageInfo } from './PackageInfo'
|
||||
import { ComponentPackageInfo, ThemePackageInfo } from './PackageInfo'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item'
|
||||
import { ComponentContent } from './ComponentContent'
|
||||
|
||||
export interface ComponentInterface extends DecryptedItemInterface<ComponentContent> {
|
||||
export interface ComponentInterface<P extends ComponentPackageInfo | ThemePackageInfo = ComponentPackageInfo>
|
||||
extends DecryptedItemInterface<ComponentContent> {
|
||||
/** Items that have requested a component to be disabled in its context */
|
||||
disassociatedItemIds: string[]
|
||||
|
||||
@@ -16,16 +17,18 @@ export interface ComponentInterface extends DecryptedItemInterface<ComponentCont
|
||||
offlineOnly: boolean
|
||||
name: string
|
||||
autoupdateDisabled: boolean
|
||||
package_info: ComponentPackageInfo
|
||||
package_info: P
|
||||
area: ComponentArea
|
||||
permissions: ComponentPermission[]
|
||||
valid_until: Date
|
||||
isMobileDefault: boolean
|
||||
isDeprecated: boolean
|
||||
|
||||
isExplicitlyEnabledForItem(uuid: string): boolean
|
||||
hasValidHostedUrl(): boolean
|
||||
|
||||
isTheme(): boolean
|
||||
get layerableTheme(): boolean
|
||||
|
||||
isExplicitlyDisabledForItem(uuid: string): boolean
|
||||
legacyIsDefaultEditor(): boolean
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { addIfUnique, removeFromArray } from '@standardnotes/utils'
|
||||
import { ComponentFeatureDescription, ComponentPermission } from '@standardnotes/features'
|
||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
||||
import { ComponentContent } from './ComponentContent'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
|
||||
export class ComponentMutator extends DecryptedItemMutator<ComponentContent> {
|
||||
set isMobileDefault(isMobileDefault: boolean) {
|
||||
this.mutableContent.isMobileDefault = isMobileDefault
|
||||
}
|
||||
|
||||
set package_info(package_info: ComponentFeatureDescription) {
|
||||
this.mutableContent.package_info = package_info
|
||||
}
|
||||
@@ -56,8 +51,4 @@ export class ComponentMutator extends DecryptedItemMutator<ComponentContent> {
|
||||
public removeDisassociatedItemId(uuid: string): void {
|
||||
removeFromArray(this.mutableContent.disassociatedItemIds || [], uuid)
|
||||
}
|
||||
|
||||
public setLastSize(size: string): void {
|
||||
this.setAppDataItem(AppDataField.LastSize, size)
|
||||
}
|
||||
}
|
||||
|
||||
29
packages/models/src/Domain/Syncable/Component/TypeGuards.ts
Normal file
29
packages/models/src/Domain/Syncable/Component/TypeGuards.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ComponentInterface } from './ComponentInterface'
|
||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { isDecryptedItem } from '../../Abstract/Item'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
export function isComponent(x: ItemInterface): x is ComponentInterface {
|
||||
if (!isDecryptedItem(x as DecryptedItemInterface)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return x.content_type === ContentType.TYPES.Component
|
||||
}
|
||||
|
||||
export function isTheme(x: ItemInterface): x is ComponentInterface {
|
||||
if (!isDecryptedItem(x as DecryptedItemInterface)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return x.content_type === ContentType.TYPES.Theme
|
||||
}
|
||||
|
||||
export function isComponentOrTheme(x: ItemInterface): x is ComponentInterface {
|
||||
if (!isDecryptedItem(x as DecryptedItemInterface)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return x.content_type === ContentType.TYPES.Component || x.content_type === ContentType.TYPES.Theme
|
||||
}
|
||||
@@ -2,5 +2,5 @@ export * from './Component'
|
||||
export * from './ComponentMutator'
|
||||
export * from './ComponentContent'
|
||||
export * from './ComponentInterface'
|
||||
export * from '../../Runtime/Feature/UIFeature'
|
||||
export * from './PackageInfo'
|
||||
export * from './TypeGuards'
|
||||
|
||||
@@ -14,7 +14,7 @@ interface EditorContent extends ItemContent {
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Editor objects are depracated in favor of SNComponent objects
|
||||
* Editor objects are depracated in favor of ComponentItem objects
|
||||
*/
|
||||
export class SNEditor extends DecryptedItem<EditorContent> {
|
||||
public readonly notes: SNNote[] = []
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { ComponentArea } from '@standardnotes/features'
|
||||
import { SNComponent } from '../Component/Component'
|
||||
import { ItemInterface } from '../../Abstract/Item'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { useBoolean } from '@standardnotes/utils'
|
||||
import { ThemePackageInfo } from '../Component/PackageInfo'
|
||||
import { ThemeInterface } from './ThemeInterface'
|
||||
|
||||
export const isTheme = (x: ItemInterface): x is ThemeInterface => x.content_type === ContentType.TYPES.Theme
|
||||
|
||||
export class SNTheme extends SNComponent implements ThemeInterface {
|
||||
public override area: ComponentArea = ComponentArea.Themes
|
||||
public declare readonly package_info: ThemePackageInfo
|
||||
|
||||
get layerable(): boolean {
|
||||
return useBoolean(this.package_info && this.package_info.layerable, false)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { ComponentInterface } from '../Component'
|
||||
import { ThemePackageInfo } from '../Component/PackageInfo'
|
||||
|
||||
export interface ThemeInterface extends ComponentInterface {
|
||||
get layerable(): boolean
|
||||
readonly package_info: ThemePackageInfo
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { ComponentContent } from '../Component/ComponentContent'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
|
||||
export class ThemeMutator extends DecryptedItemMutator<ComponentContent> {}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './Theme'
|
||||
export * from './ThemeMutator'
|
||||
export * from './ThemeInterface'
|
||||
@@ -3,17 +3,15 @@ import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/Dec
|
||||
import { FileItem } from '../../Syncable/File/File'
|
||||
import { SNFeatureRepo } from '../../Syncable/FeatureRepo/FeatureRepo'
|
||||
import { SNActionsExtension } from '../../Syncable/ActionsExtension/ActionsExtension'
|
||||
import { SNComponent } from '../../Syncable/Component/Component'
|
||||
import { ComponentItem } from '../../Syncable/Component/Component'
|
||||
import { SNEditor } from '../../Syncable/Editor/Editor'
|
||||
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
||||
import { SNNote } from '../../Syncable/Note/Note'
|
||||
import { SmartView } from '../../Syncable/SmartView/SmartView'
|
||||
import { SNTag } from '../../Syncable/Tag/Tag'
|
||||
import { SNTheme } from '../../Syncable/Theme/Theme'
|
||||
import { SNUserPrefs } from '../../Syncable/UserPrefs/UserPrefs'
|
||||
import { FileMutator } from '../../Syncable/File/FileMutator'
|
||||
import { MutationType } from '../../Abstract/Item/Types/MutationType'
|
||||
import { ThemeMutator } from '../../Syncable/Theme/ThemeMutator'
|
||||
import { UserPrefsMutator } from '../../Syncable/UserPrefs/UserPrefsMutator'
|
||||
import { ActionsExtensionMutator } from '../../Syncable/ActionsExtension/ActionsExtensionMutator'
|
||||
import { ComponentMutator } from '../../Syncable/Component/ComponentMutator'
|
||||
@@ -58,7 +56,7 @@ const ContentTypeClassMapping: Partial<Record<string, MappingEntry>> = {
|
||||
itemClass: SNActionsExtension,
|
||||
mutatorClass: ActionsExtensionMutator,
|
||||
},
|
||||
[ContentType.TYPES.Component]: { itemClass: SNComponent, mutatorClass: ComponentMutator },
|
||||
[ContentType.TYPES.Component]: { itemClass: ComponentItem, mutatorClass: ComponentMutator },
|
||||
[ContentType.TYPES.KeySystemRootKey]: { itemClass: KeySystemRootKey, mutatorClass: KeySystemRootKeyMutator },
|
||||
[ContentType.TYPES.TrustedContact]: { itemClass: TrustedContact, mutatorClass: TrustedContactMutator },
|
||||
[ContentType.TYPES.VaultListing]: { itemClass: VaultListing, mutatorClass: VaultListingMutator },
|
||||
@@ -68,7 +66,7 @@ const ContentTypeClassMapping: Partial<Record<string, MappingEntry>> = {
|
||||
[ContentType.TYPES.Note]: { itemClass: SNNote, mutatorClass: NoteMutator },
|
||||
[ContentType.TYPES.SmartView]: { itemClass: SmartView, mutatorClass: SmartViewMutator },
|
||||
[ContentType.TYPES.Tag]: { itemClass: SNTag, mutatorClass: TagMutator },
|
||||
[ContentType.TYPES.Theme]: { itemClass: SNTheme, mutatorClass: ThemeMutator },
|
||||
[ContentType.TYPES.Theme]: { itemClass: ComponentItem, mutatorClass: ComponentMutator },
|
||||
[ContentType.TYPES.UserPrefs]: { itemClass: SNUserPrefs, mutatorClass: UserPrefsMutator },
|
||||
} as unknown as Partial<Record<string, MappingEntry>>
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ export * from './Syncable/ItemsKey/ItemsKeyMutatorInterface'
|
||||
export * from './Syncable/Note'
|
||||
export * from './Syncable/SmartView'
|
||||
export * from './Syncable/Tag'
|
||||
export * from './Syncable/Theme'
|
||||
export * from './Syncable/UserPrefs'
|
||||
|
||||
export * from './Syncable/TrustedContact/TrustedContact'
|
||||
|
||||
Reference in New Issue
Block a user