Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: merge themes into components #2388

Merged
merged 4 commits into from Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 11 additions & 3 deletions packages/models/src/Domain/Runtime/Feature/UIFeature.ts
Expand Up @@ -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) {}
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
10 changes: 5 additions & 5 deletions packages/models/src/Domain/Syncable/Component/Component.spec.ts
Expand Up @@ -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()),
Expand All @@ -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()),
Expand All @@ -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()),
Expand All @@ -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()),
Expand Down
66 changes: 20 additions & 46 deletions packages/models/src/Domain/Syncable/Component/Component.ts
Expand Up @@ -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[]
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
}
Expand Up @@ -21,7 +21,6 @@ export type ComponentContentSpecialized = {
valid_until: Date | number

legacy_url?: string
isMobileDefault?: boolean
isDeprecated?: boolean

/** @deprecated */
Expand Down
@@ -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[]

Expand All @@ -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

Expand Down
@@ -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
}
Expand Down Expand Up @@ -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 changes: 29 additions & 0 deletions packages/models/src/Domain/Syncable/Component/TypeGuards.ts
@@ -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 changes: 1 addition & 1 deletion packages/models/src/Domain/Syncable/Component/index.ts
Expand Up @@ -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'
2 changes: 1 addition & 1 deletion packages/models/src/Domain/Syncable/Editor/Editor.ts
Expand Up @@ -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[] = []
Expand Down
18 changes: 0 additions & 18 deletions packages/models/src/Domain/Syncable/Theme/Theme.ts

This file was deleted.

7 changes: 0 additions & 7 deletions packages/models/src/Domain/Syncable/Theme/ThemeInterface.ts

This file was deleted.

4 changes: 0 additions & 4 deletions packages/models/src/Domain/Syncable/Theme/ThemeMutator.ts

This file was deleted.

3 changes: 0 additions & 3 deletions packages/models/src/Domain/Syncable/Theme/index.ts

This file was deleted.