feat: edit smart view predicate as json (#2012)
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
import { MutationType } from '../../Abstract/Item'
|
||||||
|
import { createSmartViewWithContent, createSmartViewWithTitle } from '../../Utilities/Test/SpecUtils'
|
||||||
|
import { SmartViewMutator } from './SmartViewMutator'
|
||||||
|
|
||||||
|
describe('smart view mutator', () => {
|
||||||
|
it('should set predicate', () => {
|
||||||
|
const smartView = createSmartViewWithContent({
|
||||||
|
title: 'foo',
|
||||||
|
predicate: {
|
||||||
|
keypath: 'title',
|
||||||
|
operator: '=',
|
||||||
|
value: 'foo',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const mutator = new SmartViewMutator(smartView, MutationType.UpdateUserTimestamps)
|
||||||
|
mutator.predicate = {
|
||||||
|
keypath: 'title',
|
||||||
|
operator: '=',
|
||||||
|
value: 'bar',
|
||||||
|
}
|
||||||
|
const result = mutator.getResult()
|
||||||
|
|
||||||
|
expect(result.content.predicate.value).toBe('bar')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preferences should be undefined if previously undefined', () => {
|
||||||
|
const smartView = createSmartViewWithTitle()
|
||||||
|
const mutator = new SmartViewMutator(smartView, MutationType.UpdateUserTimestamps)
|
||||||
|
const result = mutator.getResult()
|
||||||
|
|
||||||
|
expect(result.content.preferences).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preferences should be lazy-created if attempting to set a property', () => {
|
||||||
|
const smartView = createSmartViewWithTitle()
|
||||||
|
const mutator = new SmartViewMutator(smartView, MutationType.UpdateUserTimestamps)
|
||||||
|
mutator.preferences.sortBy = 'content_type'
|
||||||
|
const result = mutator.getResult()
|
||||||
|
|
||||||
|
expect(result.content.preferences?.sortBy).toEqual('content_type')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preferences should be nulled if client is reseting', () => {
|
||||||
|
const smartView = createSmartViewWithContent({
|
||||||
|
title: 'foo',
|
||||||
|
preferences: {
|
||||||
|
sortBy: 'content_type',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const mutator = new SmartViewMutator(smartView, MutationType.UpdateUserTimestamps)
|
||||||
|
mutator.preferences = undefined
|
||||||
|
const result = mutator.getResult()
|
||||||
|
|
||||||
|
expect(result.content.preferences).toBeFalsy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { DecryptedItemInterface, MutationType } from '../../Abstract/Item'
|
||||||
|
import { TagMutator } from '../Tag'
|
||||||
|
import { SmartViewContent } from './SmartViewContent'
|
||||||
|
|
||||||
|
export class SmartViewMutator extends TagMutator<SmartViewContent> {
|
||||||
|
constructor(item: DecryptedItemInterface<SmartViewContent>, type: MutationType) {
|
||||||
|
super(item, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
set predicate(predicate: SmartViewContent['predicate']) {
|
||||||
|
this.mutableContent.predicate = predicate
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,3 +2,5 @@ export * from './SmartView'
|
|||||||
export * from './SmartViewBuilder'
|
export * from './SmartViewBuilder'
|
||||||
export * from './SystemViewId'
|
export * from './SystemViewId'
|
||||||
export * from './SmartViewContent'
|
export * from './SmartViewContent'
|
||||||
|
export * from './SmartViewMutator'
|
||||||
|
export * from './SmartViewIcons'
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { TagToFileReference } from '../../Abstract/Reference/TagToFileReference'
|
|||||||
import { TagPreferences } from './TagPreferences'
|
import { TagPreferences } from './TagPreferences'
|
||||||
import { DecryptedItemInterface, MutationType } from '../../Abstract/Item'
|
import { DecryptedItemInterface, MutationType } from '../../Abstract/Item'
|
||||||
|
|
||||||
export class TagMutator extends DecryptedItemMutator<TagContent> {
|
export class TagMutator<Content extends TagContent = TagContent> extends DecryptedItemMutator<Content> {
|
||||||
private mutablePreferences?: TagPreferences
|
private mutablePreferences?: TagPreferences
|
||||||
|
|
||||||
constructor(item: DecryptedItemInterface<TagContent>, type: MutationType) {
|
constructor(item: DecryptedItemInterface<Content>, type: MutationType) {
|
||||||
super(item, type)
|
super(item, type)
|
||||||
|
|
||||||
this.mutablePreferences = this.mutableContent.preferences
|
this.mutablePreferences = this.mutableContent.preferences
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
import { DeletedItem } from '../../Abstract/Item/Implementations/DeletedItem'
|
import { DeletedItem } from '../../Abstract/Item/Implementations/DeletedItem'
|
||||||
import { EncryptedItemInterface } from '../../Abstract/Item/Interfaces/EncryptedItem'
|
import { EncryptedItemInterface } from '../../Abstract/Item/Interfaces/EncryptedItem'
|
||||||
import { DeletedItemInterface } from '../../Abstract/Item/Interfaces/DeletedItem'
|
import { DeletedItemInterface } from '../../Abstract/Item/Interfaces/DeletedItem'
|
||||||
|
import { SmartViewMutator } from '../../Syncable/SmartView'
|
||||||
|
|
||||||
type ItemClass<C extends ItemContent = ItemContent> = new (payload: DecryptedPayloadInterface<C>) => DecryptedItem<C>
|
type ItemClass<C extends ItemContent = ItemContent> = new (payload: DecryptedPayloadInterface<C>) => DecryptedItem<C>
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ const ContentTypeClassMapping: Partial<Record<ContentType, MappingEntry>> = {
|
|||||||
[ContentType.ExtensionRepo]: { itemClass: SNFeatureRepo },
|
[ContentType.ExtensionRepo]: { itemClass: SNFeatureRepo },
|
||||||
[ContentType.File]: { itemClass: FileItem, mutatorClass: FileMutator },
|
[ContentType.File]: { itemClass: FileItem, mutatorClass: FileMutator },
|
||||||
[ContentType.Note]: { itemClass: SNNote, mutatorClass: NoteMutator },
|
[ContentType.Note]: { itemClass: SNNote, mutatorClass: NoteMutator },
|
||||||
[ContentType.SmartView]: { itemClass: SmartView, mutatorClass: TagMutator },
|
[ContentType.SmartView]: { itemClass: SmartView, mutatorClass: SmartViewMutator },
|
||||||
[ContentType.Tag]: { itemClass: SNTag, mutatorClass: TagMutator },
|
[ContentType.Tag]: { itemClass: SNTag, mutatorClass: TagMutator },
|
||||||
[ContentType.Theme]: { itemClass: SNTheme, mutatorClass: ThemeMutator },
|
[ContentType.Theme]: { itemClass: SNTheme, mutatorClass: ThemeMutator },
|
||||||
[ContentType.UserPrefs]: { itemClass: SNUserPrefs, mutatorClass: UserPrefsMutator },
|
[ContentType.UserPrefs]: { itemClass: SNUserPrefs, mutatorClass: UserPrefsMutator },
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { DecryptedPayload, PayloadSource, PayloadTimestampDefaults } from '../..
|
|||||||
import { FileContent, FileItem } from '../../Syncable/File'
|
import { FileContent, FileItem } from '../../Syncable/File'
|
||||||
import { NoteContent, SNNote } from '../../Syncable/Note'
|
import { NoteContent, SNNote } from '../../Syncable/Note'
|
||||||
import { SNTag } from '../../Syncable/Tag'
|
import { SNTag } from '../../Syncable/Tag'
|
||||||
|
import { SmartView, SmartViewContent } from '../../Syncable/SmartView'
|
||||||
|
|
||||||
let currentId = 0
|
let currentId = 0
|
||||||
|
|
||||||
@@ -55,6 +56,20 @@ export const createTagWithContent = (content: Partial<TagContent>): SNTag => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createSmartViewWithContent = (content: Partial<SmartViewContent>): SmartView => {
|
||||||
|
return new SmartView(
|
||||||
|
new DecryptedPayload(
|
||||||
|
{
|
||||||
|
uuid: mockUuid(),
|
||||||
|
content_type: ContentType.SmartView,
|
||||||
|
content: FillItemContent<SmartViewContent>(content),
|
||||||
|
...PayloadTimestampDefaults(),
|
||||||
|
},
|
||||||
|
PayloadSource.Constructor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const createTagWithTitle = (title = 'photos') => {
|
export const createTagWithTitle = (title = 'photos') => {
|
||||||
return new SNTag(
|
return new SNTag(
|
||||||
new DecryptedPayload(
|
new DecryptedPayload(
|
||||||
@@ -69,6 +84,20 @@ export const createTagWithTitle = (title = 'photos') => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createSmartViewWithTitle = (title = 'photos') => {
|
||||||
|
return new SmartView(
|
||||||
|
new DecryptedPayload(
|
||||||
|
{
|
||||||
|
uuid: mockUuid(),
|
||||||
|
content_type: ContentType.SmartView,
|
||||||
|
content: FillItemContent<SmartViewContent>({ title }),
|
||||||
|
...PayloadTimestampDefaults(),
|
||||||
|
},
|
||||||
|
PayloadSource.Constructor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const createFile = (name = 'screenshot.png') => {
|
export const createFile = (name = 'screenshot.png') => {
|
||||||
return new FileItem(
|
return new FileItem(
|
||||||
new DecryptedPayload(
|
new DecryptedPayload(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as Services from '@standardnotes/services'
|
|||||||
import { PayloadManagerChangeData } from '../Payloads'
|
import { PayloadManagerChangeData } from '../Payloads'
|
||||||
import { DiagnosticInfo, ItemsClientInterface, ItemRelationshipDirection } from '@standardnotes/services'
|
import { DiagnosticInfo, ItemsClientInterface, ItemRelationshipDirection } from '@standardnotes/services'
|
||||||
import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions'
|
import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions'
|
||||||
import { CollectionSort, DecryptedItemInterface, ItemContent } from '@standardnotes/models'
|
import { CollectionSort, DecryptedItemInterface, ItemContent, SmartViewDefaultIconName } from '@standardnotes/models'
|
||||||
|
|
||||||
type ItemsChangeObserver<I extends Models.DecryptedItemInterface = Models.DecryptedItemInterface> = {
|
type ItemsChangeObserver<I extends Models.DecryptedItemInterface = Models.DecryptedItemInterface> = {
|
||||||
contentType: ContentType[]
|
contentType: ContentType[]
|
||||||
@@ -1229,7 +1229,7 @@ export class ItemManager
|
|||||||
Models.FillItemContent({
|
Models.FillItemContent({
|
||||||
title,
|
title,
|
||||||
predicate: predicate.toJson(),
|
predicate: predicate.toJson(),
|
||||||
iconString: iconString || 'restore',
|
iconString: iconString || SmartViewDefaultIconName,
|
||||||
} as Models.SmartViewContent),
|
} as Models.SmartViewContent),
|
||||||
true,
|
true,
|
||||||
) as Promise<Models.SmartView>
|
) as Promise<Models.SmartView>
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ const General: FunctionComponent<Props> = ({ viewControllerManager, application,
|
|||||||
<Persistence application={application} />
|
<Persistence application={application} />
|
||||||
<PlaintextDefaults application={application} />
|
<PlaintextDefaults application={application} />
|
||||||
<Defaults application={application} />
|
<Defaults application={application} />
|
||||||
<SmartViews
|
<SmartViews application={application} featuresController={viewControllerManager.featuresController} />
|
||||||
application={application}
|
|
||||||
navigationController={viewControllerManager.navigationController}
|
|
||||||
featuresController={viewControllerManager.featuresController}
|
|
||||||
/>
|
|
||||||
<Tools application={application} />
|
<Tools application={application} />
|
||||||
<LabsPane application={application} />
|
<LabsPane application={application} />
|
||||||
<Advanced
|
<Advanced
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
|
||||||
import Button from '@/Components/Button/Button'
|
import Button from '@/Components/Button/Button'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import IconPicker from '@/Components/Icon/IconPicker'
|
import IconPicker from '@/Components/Icon/IconPicker'
|
||||||
@@ -8,25 +7,35 @@ import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
|||||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||||
import Spinner from '@/Components/Spinner/Spinner'
|
import Spinner from '@/Components/Spinner/Spinner'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { Platform, SmartViewDefaultIconName } from '@standardnotes/snjs'
|
||||||
import { SmartView, TagMutator } from '@standardnotes/snjs'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { useCallback, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import { EditSmartViewModalController } from './EditSmartViewModalController'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
controller: EditSmartViewModalController
|
||||||
navigationController: NavigationController
|
platform: Platform
|
||||||
view: SmartView
|
|
||||||
closeDialog: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditSmartViewModal = ({ application, navigationController, view, closeDialog }: Props) => {
|
const EditSmartViewModal = ({ controller, platform }: Props) => {
|
||||||
const [title, setTitle] = useState(view.title)
|
const {
|
||||||
|
view,
|
||||||
|
title,
|
||||||
|
setTitle,
|
||||||
|
predicateJson,
|
||||||
|
setPredicateJson,
|
||||||
|
isPredicateJsonValid,
|
||||||
|
setIsPredicateJsonValid,
|
||||||
|
icon,
|
||||||
|
setIcon,
|
||||||
|
save,
|
||||||
|
isSaving,
|
||||||
|
closeDialog,
|
||||||
|
deleteView,
|
||||||
|
} = controller
|
||||||
|
|
||||||
const titleInputRef = useRef<HTMLInputElement>(null)
|
const titleInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const predicateJsonInputRef = useRef<HTMLTextAreaElement>(null)
|
||||||
const [selectedIcon, setSelectedIcon] = useState<string | undefined>(view.iconString)
|
|
||||||
|
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
|
||||||
|
|
||||||
const [shouldShowIconPicker, setShouldShowIconPicker] = useState(false)
|
const [shouldShowIconPicker, setShouldShowIconPicker] = useState(false)
|
||||||
const iconPickerButtonRef = useRef<HTMLButtonElement>(null)
|
const iconPickerButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
@@ -41,29 +50,26 @@ const EditSmartViewModal = ({ application, navigationController, view, closeDial
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSaving(true)
|
void save()
|
||||||
|
}, [save, title.length])
|
||||||
|
|
||||||
await application.mutator.changeAndSaveItem<TagMutator>(view, (mutator) => {
|
useEffect(() => {
|
||||||
mutator.title = title
|
if (!predicateJsonInputRef.current) {
|
||||||
mutator.iconString = selectedIcon || 'restore'
|
return
|
||||||
})
|
}
|
||||||
|
|
||||||
setIsSaving(false)
|
if (isPredicateJsonValid === false) {
|
||||||
closeDialog()
|
predicateJsonInputRef.current.focus()
|
||||||
}, [application.mutator, closeDialog, selectedIcon, title, view])
|
}
|
||||||
|
}, [isPredicateJsonValid])
|
||||||
|
|
||||||
const deleteSmartView = useCallback(async () => {
|
if (!view) {
|
||||||
void navigationController.remove(view, true)
|
return null
|
||||||
closeDialog()
|
}
|
||||||
}, [closeDialog, navigationController, view])
|
|
||||||
|
|
||||||
const close = useCallback(() => {
|
|
||||||
closeDialog()
|
|
||||||
}, [closeDialog])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalDialog>
|
<ModalDialog>
|
||||||
<ModalDialogLabel closeDialog={close}>Edit Smart View "{view.title}"</ModalDialogLabel>
|
<ModalDialogLabel closeDialog={closeDialog}>Edit Smart View "{view.title}"</ModalDialogLabel>
|
||||||
<ModalDialogDescription>
|
<ModalDialogDescription>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
@@ -85,7 +91,7 @@ const EditSmartViewModal = ({ application, navigationController, view, closeDial
|
|||||||
onClick={toggleIconPicker}
|
onClick={toggleIconPicker}
|
||||||
ref={iconPickerButtonRef}
|
ref={iconPickerButtonRef}
|
||||||
>
|
>
|
||||||
<Icon type={selectedIcon || 'restore'} />
|
<Icon type={icon || SmartViewDefaultIconName} />
|
||||||
</button>
|
</button>
|
||||||
<Popover
|
<Popover
|
||||||
open={shouldShowIconPicker}
|
open={shouldShowIconPicker}
|
||||||
@@ -96,28 +102,48 @@ const EditSmartViewModal = ({ application, navigationController, view, closeDial
|
|||||||
>
|
>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<IconPicker
|
<IconPicker
|
||||||
selectedValue={selectedIcon || 'restore'}
|
selectedValue={icon || SmartViewDefaultIconName}
|
||||||
onIconChange={(value?: string | undefined) => {
|
onIconChange={(value?: string | undefined) => {
|
||||||
setSelectedIcon(value)
|
setIcon(value || SmartViewDefaultIconName)
|
||||||
toggleIconPicker()
|
toggleIconPicker()
|
||||||
}}
|
}}
|
||||||
platform={application.platform}
|
platform={platform}
|
||||||
useIconGrid={true}
|
useIconGrid={true}
|
||||||
portalDropdown={false}
|
portalDropdown={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-2.5">
|
||||||
|
<div className="text-sm font-semibold">Predicate:</div>
|
||||||
|
<div className="flex flex-col overflow-hidden rounded-md border border-border">
|
||||||
|
<textarea
|
||||||
|
className="h-full min-h-[10rem] w-full flex-grow resize-none bg-default py-1.5 px-2.5 font-mono text-sm"
|
||||||
|
value={predicateJson}
|
||||||
|
onChange={(event) => {
|
||||||
|
setPredicateJson(event.target.value)
|
||||||
|
setIsPredicateJsonValid(true)
|
||||||
|
}}
|
||||||
|
spellCheck={false}
|
||||||
|
ref={predicateJsonInputRef}
|
||||||
|
/>
|
||||||
|
{!isPredicateJsonValid && (
|
||||||
|
<div className="border-t border-border px-2.5 py-1.5 text-sm text-danger">
|
||||||
|
Invalid JSON. Double check your entry and try again.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalDialogDescription>
|
</ModalDialogDescription>
|
||||||
<ModalDialogButtons>
|
<ModalDialogButtons>
|
||||||
<Button className="mr-auto" disabled={isSaving} onClick={deleteSmartView} colorStyle="danger">
|
<Button className="mr-auto" disabled={isSaving} onClick={deleteView} colorStyle="danger">
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={isSaving} onClick={saveSmartView}>
|
<Button disabled={isSaving} onClick={saveSmartView} primary colorStyle="info">
|
||||||
{isSaving ? <Spinner className="h-4.5 w-4.5" /> : 'Save'}
|
{isSaving ? <Spinner className="h-4.5 w-4.5" /> : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={isSaving} onClick={close}>
|
<Button disabled={isSaving} onClick={closeDialog}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</ModalDialogButtons>
|
</ModalDialogButtons>
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import { WebApplication } from '@/Application/Application'
|
||||||
|
import { STRING_DELETE_TAG } from '@/Constants/Strings'
|
||||||
|
import {
|
||||||
|
predicateFromJson,
|
||||||
|
PredicateJsonForm,
|
||||||
|
SmartView,
|
||||||
|
SmartViewDefaultIconName,
|
||||||
|
SmartViewMutator,
|
||||||
|
} from '@standardnotes/snjs'
|
||||||
|
import { confirmDialog } from '@standardnotes/ui-services'
|
||||||
|
import { action, makeObservable, observable } from 'mobx'
|
||||||
|
|
||||||
|
export class EditSmartViewModalController {
|
||||||
|
title = ''
|
||||||
|
icon: string = SmartViewDefaultIconName
|
||||||
|
predicateJson = ''
|
||||||
|
isPredicateJsonValid = false
|
||||||
|
isSaving = false
|
||||||
|
view: SmartView | undefined = undefined
|
||||||
|
|
||||||
|
constructor(private application: WebApplication) {
|
||||||
|
makeObservable(this, {
|
||||||
|
title: observable,
|
||||||
|
icon: observable,
|
||||||
|
predicateJson: observable,
|
||||||
|
isPredicateJsonValid: observable,
|
||||||
|
isSaving: observable,
|
||||||
|
view: observable,
|
||||||
|
|
||||||
|
setTitle: action,
|
||||||
|
setIcon: action,
|
||||||
|
setPredicateJson: action,
|
||||||
|
setIsPredicateJsonValid: action,
|
||||||
|
setIsSaving: action,
|
||||||
|
setView: action,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitle = (title: string) => {
|
||||||
|
this.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
setIcon = (icon: string) => {
|
||||||
|
this.icon = icon
|
||||||
|
}
|
||||||
|
|
||||||
|
setPredicateJson = (json: string) => {
|
||||||
|
this.predicateJson = json
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPredicateJsonValid = (isValid: boolean) => {
|
||||||
|
this.isPredicateJsonValid = isValid
|
||||||
|
}
|
||||||
|
|
||||||
|
setView = (view: SmartView | undefined) => {
|
||||||
|
this.view = view
|
||||||
|
|
||||||
|
if (view) {
|
||||||
|
this.setTitle(view.title)
|
||||||
|
this.setIcon(view.iconString)
|
||||||
|
this.setPredicateJson(JSON.stringify(view.predicate.toJson(), null, 2))
|
||||||
|
this.setIsPredicateJsonValid(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSaving = (isSaving: boolean) => {
|
||||||
|
this.isSaving = isSaving
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog = () => {
|
||||||
|
this.setView(undefined)
|
||||||
|
this.setTitle('')
|
||||||
|
this.setIcon(SmartViewDefaultIconName)
|
||||||
|
this.setPredicateJson('')
|
||||||
|
}
|
||||||
|
|
||||||
|
save = async () => {
|
||||||
|
if (!this.view) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.validateAndPrettifyCustomPredicate()
|
||||||
|
|
||||||
|
if (!this.isPredicateJsonValid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setIsSaving(true)
|
||||||
|
|
||||||
|
await this.application.mutator.changeAndSaveItem<SmartViewMutator>(this.view, (mutator) => {
|
||||||
|
mutator.title = this.title
|
||||||
|
mutator.iconString = this.icon || SmartViewDefaultIconName
|
||||||
|
mutator.predicate = JSON.parse(this.predicateJson) as PredicateJsonForm
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setIsSaving(false)
|
||||||
|
this.closeDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteView = async () => {
|
||||||
|
if (!this.view) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const view = this.view
|
||||||
|
|
||||||
|
this.closeDialog()
|
||||||
|
|
||||||
|
const shouldDelete = await confirmDialog({
|
||||||
|
text: STRING_DELETE_TAG,
|
||||||
|
confirmButtonStyle: 'danger',
|
||||||
|
})
|
||||||
|
if (shouldDelete) {
|
||||||
|
this.application.mutator.deleteItem(view).catch(console.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateAndPrettifyCustomPredicate = () => {
|
||||||
|
try {
|
||||||
|
const parsedPredicate: PredicateJsonForm = JSON.parse(this.predicateJson)
|
||||||
|
const predicate = predicateFromJson(parsedPredicate)
|
||||||
|
|
||||||
|
if (predicate) {
|
||||||
|
this.setPredicateJson(JSON.stringify(parsedPredicate, null, 2))
|
||||||
|
this.setIsPredicateJsonValid(true)
|
||||||
|
} else {
|
||||||
|
this.setIsPredicateJsonValid(false)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.setIsPredicateJsonValid(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import Button from '@/Components/Button/Button'
|
import Button from '@/Components/Button/Button'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import { SmartView } from '@standardnotes/snjs'
|
import { SmartView } from '@standardnotes/snjs'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
view: SmartView
|
view: SmartView
|
||||||
onEdit: () => void
|
onEdit: () => void
|
||||||
onDelete: () => void
|
onDelete: (view: SmartView) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SmartViewItem = ({ view, onEdit, onDelete }: Props) => {
|
const SmartViewItem = ({ view, onEdit, onDelete }: Props) => {
|
||||||
|
const onClickDelete = useCallback(() => onDelete(view), [onDelete, view])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 py-1.5">
|
<div className="flex items-center gap-2 py-1.5">
|
||||||
<Icon type={view.iconString} size="custom" className="h-5.5 w-5.5" />
|
<Icon type={view.iconString} size="custom" className="h-5.5 w-5.5" />
|
||||||
@@ -16,7 +19,7 @@ const SmartViewItem = ({ view, onEdit, onDelete }: Props) => {
|
|||||||
<Button small onClick={onEdit}>
|
<Button small onClick={onEdit}>
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
<Button small onClick={onDelete}>
|
<Button small onClick={onClickDelete}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import Button from '@/Components/Button/Button'
|
import Button from '@/Components/Button/Button'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { ContentType, isSystemView, SmartView } from '@standardnotes/snjs'
|
||||||
import { isSystemView, SmartView } from '@standardnotes/snjs'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { Title } from '../../../PreferencesComponents/Content'
|
import { Title } from '../../../PreferencesComponents/Content'
|
||||||
import PreferencesGroup from '../../../PreferencesComponents/PreferencesGroup'
|
import PreferencesGroup from '../../../PreferencesComponents/PreferencesGroup'
|
||||||
import PreferencesSegment from '../../../PreferencesComponents/PreferencesSegment'
|
import PreferencesSegment from '../../../PreferencesComponents/PreferencesSegment'
|
||||||
@@ -13,21 +12,45 @@ import EditSmartViewModal from './EditSmartViewModal'
|
|||||||
import SmartViewItem from './SmartViewItem'
|
import SmartViewItem from './SmartViewItem'
|
||||||
import { FeaturesController } from '@/Controllers/FeaturesController'
|
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||||
import NoSubscriptionBanner from '@/Components/NoSubscriptionBanner/NoSubscriptionBanner'
|
import NoSubscriptionBanner from '@/Components/NoSubscriptionBanner/NoSubscriptionBanner'
|
||||||
|
import { EditSmartViewModalController } from './EditSmartViewModalController'
|
||||||
|
import { STRING_DELETE_TAG } from '@/Constants/Strings'
|
||||||
|
import { confirmDialog } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
type NewType = {
|
type NewType = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
navigationController: NavigationController
|
|
||||||
featuresController: FeaturesController
|
featuresController: FeaturesController
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = NewType
|
type Props = NewType
|
||||||
|
|
||||||
const SmartViews = ({ application, navigationController, featuresController }: Props) => {
|
const SmartViews = ({ application, featuresController }: Props) => {
|
||||||
const [editingSmartView, setEditingSmartView] = useState<SmartView | undefined>(undefined)
|
|
||||||
|
|
||||||
const addSmartViewModalController = useMemo(() => new AddSmartViewModalController(application), [application])
|
const addSmartViewModalController = useMemo(() => new AddSmartViewModalController(application), [application])
|
||||||
|
const editSmartViewModalController = useMemo(() => new EditSmartViewModalController(application), [application])
|
||||||
|
|
||||||
const nonSystemSmartViews = navigationController.smartViews.filter((view) => !isSystemView(view))
|
const [smartViews, setSmartViews] = useState(() =>
|
||||||
|
application.items.getSmartViews().filter((view) => !isSystemView(view)),
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const disposeItemStream = application.streamItems([ContentType.SmartView], () => {
|
||||||
|
setSmartViews(application.items.getSmartViews().filter((view) => !isSystemView(view)))
|
||||||
|
})
|
||||||
|
|
||||||
|
return disposeItemStream
|
||||||
|
}, [application])
|
||||||
|
|
||||||
|
const deleteItem = useCallback(
|
||||||
|
async (view: SmartView) => {
|
||||||
|
const shouldDelete = await confirmDialog({
|
||||||
|
text: STRING_DELETE_TAG,
|
||||||
|
confirmButtonStyle: 'danger',
|
||||||
|
})
|
||||||
|
if (shouldDelete) {
|
||||||
|
application.mutator.deleteItem(view).catch(console.error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[application.mutator],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -45,12 +68,12 @@ const SmartViews = ({ application, navigationController, featuresController }: P
|
|||||||
{featuresController.hasSmartViews && (
|
{featuresController.hasSmartViews && (
|
||||||
<>
|
<>
|
||||||
<div className="my-2 flex flex-col">
|
<div className="my-2 flex flex-col">
|
||||||
{nonSystemSmartViews.map((view) => (
|
{smartViews.map((view) => (
|
||||||
<SmartViewItem
|
<SmartViewItem
|
||||||
key={view.uuid}
|
key={view.uuid}
|
||||||
view={view}
|
view={view}
|
||||||
onEdit={() => setEditingSmartView(view)}
|
onEdit={() => editSmartViewModalController.setView(view)}
|
||||||
onDelete={() => navigationController.remove(view, true)}
|
onDelete={deleteItem}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -65,15 +88,8 @@ const SmartViews = ({ application, navigationController, featuresController }: P
|
|||||||
)}
|
)}
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
{!!editingSmartView && (
|
{!!editSmartViewModalController.view && (
|
||||||
<EditSmartViewModal
|
<EditSmartViewModal controller={editSmartViewModalController} platform={application.platform} />
|
||||||
application={application}
|
|
||||||
navigationController={navigationController}
|
|
||||||
view={editingSmartView}
|
|
||||||
closeDialog={() => {
|
|
||||||
setEditingSmartView(undefined)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{addSmartViewModalController.isAddingSmartView && (
|
{addSmartViewModalController.isAddingSmartView && (
|
||||||
<AddSmartViewModal controller={addSmartViewModalController} platform={application.platform} />
|
<AddSmartViewModal controller={addSmartViewModalController} platform={application.platform} />
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
|
|||||||
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
|
||||||
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
|
||||||
import Spinner from '@/Components/Spinner/Spinner'
|
import Spinner from '@/Components/Spinner/Spinner'
|
||||||
import { Platform } from '@standardnotes/snjs'
|
import { Platform, SmartViewDefaultIconName } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { AddSmartViewModalController } from './AddSmartViewModalController'
|
import { AddSmartViewModalController } from './AddSmartViewModalController'
|
||||||
@@ -141,7 +141,7 @@ const AddSmartViewModal = ({ controller, platform }: Props) => {
|
|||||||
onClick={toggleIconPicker}
|
onClick={toggleIconPicker}
|
||||||
ref={iconPickerButtonRef}
|
ref={iconPickerButtonRef}
|
||||||
>
|
>
|
||||||
<Icon type={icon || 'restore'} />
|
<Icon type={icon || SmartViewDefaultIconName} />
|
||||||
</button>
|
</button>
|
||||||
<Popover
|
<Popover
|
||||||
open={shouldShowIconPicker}
|
open={shouldShowIconPicker}
|
||||||
@@ -152,9 +152,9 @@ const AddSmartViewModal = ({ controller, platform }: Props) => {
|
|||||||
>
|
>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<IconPicker
|
<IconPicker
|
||||||
selectedValue={icon || 'restore'}
|
selectedValue={icon || SmartViewDefaultIconName}
|
||||||
onIconChange={(value?: string | undefined) => {
|
onIconChange={(value?: string | undefined) => {
|
||||||
setIcon(value ?? 'restore')
|
setIcon(value ?? SmartViewDefaultIconName)
|
||||||
toggleIconPicker()
|
toggleIconPicker()
|
||||||
}}
|
}}
|
||||||
platform={platform}
|
platform={platform}
|
||||||
@@ -182,9 +182,9 @@ const AddSmartViewModal = ({ controller, platform }: Props) => {
|
|||||||
<TabPanel state={tabState} id="builder" className="flex flex-col gap-2.5 p-4">
|
<TabPanel state={tabState} id="builder" className="flex flex-col gap-2.5 p-4">
|
||||||
<CompoundPredicateBuilder controller={predicateController} />
|
<CompoundPredicateBuilder controller={predicateController} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel state={tabState} id="custom">
|
<TabPanel state={tabState} id="custom" className="flex flex-col">
|
||||||
<textarea
|
<textarea
|
||||||
className="h-full min-h-[10rem] w-full resize-none bg-default py-1.5 px-2.5 font-mono text-sm"
|
className="h-full min-h-[10rem] w-full flex-grow resize-none bg-default py-1.5 px-2.5 font-mono text-sm"
|
||||||
value={customPredicateJson}
|
value={customPredicateJson}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setCustomPredicateJson(event.target.value)
|
setCustomPredicateJson(event.target.value)
|
||||||
@@ -194,7 +194,7 @@ const AddSmartViewModal = ({ controller, platform }: Props) => {
|
|||||||
ref={customJsonInputRef}
|
ref={customJsonInputRef}
|
||||||
/>
|
/>
|
||||||
{customPredicateJson && isCustomJsonValidPredicate === false && (
|
{customPredicateJson && isCustomJsonValidPredicate === false && (
|
||||||
<div className="mt-2 border-t border-border px-2.5 py-1.5 text-sm text-danger">
|
<div className="border-t border-border px-2.5 py-1.5 text-sm text-danger">
|
||||||
Invalid JSON. Double check your entry and try again.
|
Invalid JSON. Double check your entry and try again.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { CompoundPredicateBuilderController } from '@/Components/SmartViewBuilder/CompoundPredicateBuilderController'
|
import { CompoundPredicateBuilderController } from '@/Components/SmartViewBuilder/CompoundPredicateBuilderController'
|
||||||
import { predicateFromJson, PredicateJsonForm } from '@standardnotes/snjs'
|
import { predicateFromJson, PredicateJsonForm, SmartViewDefaultIconName } from '@standardnotes/snjs'
|
||||||
import { action, makeObservable, observable } from 'mobx'
|
import { action, makeObservable, observable } from 'mobx'
|
||||||
|
|
||||||
export class AddSmartViewModalController {
|
export class AddSmartViewModalController {
|
||||||
@@ -9,7 +9,7 @@ export class AddSmartViewModalController {
|
|||||||
|
|
||||||
title = ''
|
title = ''
|
||||||
|
|
||||||
icon = 'restore'
|
icon: string = SmartViewDefaultIconName
|
||||||
|
|
||||||
predicateController = new CompoundPredicateBuilderController()
|
predicateController = new CompoundPredicateBuilderController()
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,11 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
|
|||||||
'md:hover:[overflow-y:_overlay]',
|
'md:hover:[overflow-y:_overlay]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SmartViewsSection application={application} viewControllerManager={viewControllerManager} />
|
<SmartViewsSection
|
||||||
|
application={application}
|
||||||
|
featuresController={viewControllerManager.featuresController}
|
||||||
|
navigationController={viewControllerManager.navigationController}
|
||||||
|
/>
|
||||||
<TagsSection viewControllerManager={viewControllerManager} />
|
<TagsSection viewControllerManager={viewControllerManager} />
|
||||||
</div>
|
</div>
|
||||||
{NavigationFooter}
|
{NavigationFooter}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||||
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { SmartView } from '@standardnotes/snjs'
|
import { SmartView } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'react'
|
import { FunctionComponent } from 'react'
|
||||||
import SmartViewsListItem from './SmartViewsListItem'
|
import SmartViewsListItem from './SmartViewsListItem'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
viewControllerManager: ViewControllerManager
|
navigationController: NavigationController
|
||||||
|
featuresController: FeaturesController
|
||||||
setEditingSmartView: (smartView: SmartView) => void
|
setEditingSmartView: (smartView: SmartView) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SmartViewsList: FunctionComponent<Props> = ({ viewControllerManager, setEditingSmartView }: Props) => {
|
const SmartViewsList: FunctionComponent<Props> = ({
|
||||||
const allViews = viewControllerManager.navigationController.smartViews
|
navigationController,
|
||||||
|
featuresController,
|
||||||
|
setEditingSmartView,
|
||||||
|
}: Props) => {
|
||||||
|
const allViews = navigationController.smartViews
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -19,8 +25,8 @@ const SmartViewsList: FunctionComponent<Props> = ({ viewControllerManager, setEd
|
|||||||
<SmartViewsListItem
|
<SmartViewsListItem
|
||||||
key={view.uuid}
|
key={view.uuid}
|
||||||
view={view}
|
view={view}
|
||||||
tagsState={viewControllerManager.navigationController}
|
tagsState={navigationController}
|
||||||
features={viewControllerManager.featuresController}
|
features={featuresController}
|
||||||
setEditingSmartView={setEditingSmartView}
|
setEditingSmartView={setEditingSmartView}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants'
|
import { SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants'
|
||||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||||
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||||
import { SmartView } from '@standardnotes/snjs'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useCallback, useMemo, useState } from 'react'
|
import { FunctionComponent, useCallback, useMemo } from 'react'
|
||||||
import IconButton from '../Button/IconButton'
|
import IconButton from '../Button/IconButton'
|
||||||
import EditSmartViewModal from '../Preferences/Panes/General/SmartViews/EditSmartViewModal'
|
import EditSmartViewModal from '../Preferences/Panes/General/SmartViews/EditSmartViewModal'
|
||||||
|
import { EditSmartViewModalController } from '../Preferences/Panes/General/SmartViews/EditSmartViewModalController'
|
||||||
import AddSmartViewModal from '../SmartViewBuilder/AddSmartViewModal'
|
import AddSmartViewModal from '../SmartViewBuilder/AddSmartViewModal'
|
||||||
import { AddSmartViewModalController } from '../SmartViewBuilder/AddSmartViewModalController'
|
import { AddSmartViewModalController } from '../SmartViewBuilder/AddSmartViewModalController'
|
||||||
import SmartViewsList from './SmartViewsList'
|
import SmartViewsList from './SmartViewsList'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
viewControllerManager: ViewControllerManager
|
navigationController: NavigationController
|
||||||
|
featuresController: FeaturesController
|
||||||
}
|
}
|
||||||
|
|
||||||
const SmartViewsSection: FunctionComponent<Props> = ({ application, viewControllerManager }) => {
|
const SmartViewsSection: FunctionComponent<Props> = ({ application, navigationController, featuresController }) => {
|
||||||
const premiumModal = usePremiumModal()
|
const premiumModal = usePremiumModal()
|
||||||
const addSmartViewModalController = useMemo(() => new AddSmartViewModalController(application), [application])
|
const addSmartViewModalController = useMemo(() => new AddSmartViewModalController(application), [application])
|
||||||
|
const editSmartViewModalController = useMemo(() => new EditSmartViewModalController(application), [application])
|
||||||
const [editingSmartView, setEditingSmartView] = useState<SmartView | undefined>(undefined)
|
|
||||||
|
|
||||||
const createNewSmartView = useCallback(() => {
|
const createNewSmartView = useCallback(() => {
|
||||||
if (!viewControllerManager.featuresController.hasSmartViews) {
|
if (!featuresController.hasSmartViews) {
|
||||||
premiumModal.activate(SMART_TAGS_FEATURE_NAME)
|
premiumModal.activate(SMART_TAGS_FEATURE_NAME)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addSmartViewModalController.setIsAddingSmartView(true)
|
addSmartViewModalController.setIsAddingSmartView(true)
|
||||||
}, [addSmartViewModalController, premiumModal, viewControllerManager.featuresController.hasSmartViews])
|
}, [addSmartViewModalController, premiumModal, featuresController.hasSmartViews])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
@@ -47,16 +48,13 @@ const SmartViewsSection: FunctionComponent<Props> = ({ application, viewControll
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SmartViewsList viewControllerManager={viewControllerManager} setEditingSmartView={setEditingSmartView} />
|
<SmartViewsList
|
||||||
{!!editingSmartView && (
|
navigationController={navigationController}
|
||||||
<EditSmartViewModal
|
featuresController={featuresController}
|
||||||
application={application}
|
setEditingSmartView={editSmartViewModalController.setView}
|
||||||
navigationController={viewControllerManager.navigationController}
|
/>
|
||||||
view={editingSmartView}
|
{!!editSmartViewModalController.view && (
|
||||||
closeDialog={() => {
|
<EditSmartViewModal controller={editSmartViewModalController} platform={application.platform} />
|
||||||
setEditingSmartView(undefined)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{addSmartViewModalController.isAddingSmartView && (
|
{addSmartViewModalController.isAddingSmartView && (
|
||||||
<AddSmartViewModal controller={addSmartViewModalController} platform={application.platform} />
|
<AddSmartViewModal controller={addSmartViewModalController} platform={application.platform} />
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export class ItemListController extends AbstractViewController implements Intern
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.disposers.push(
|
this.disposers.push(
|
||||||
application.streamItems<SNTag>([ContentType.Tag], async ({ changed, inserted }) => {
|
application.streamItems<SNTag>([ContentType.Tag, ContentType.SmartView], async ({ changed, inserted }) => {
|
||||||
const tags = [...changed, ...inserted]
|
const tags = [...changed, ...inserted]
|
||||||
|
|
||||||
const { didReloadItems } = await this.reloadDisplayPreferences()
|
const { didReloadItems } = await this.reloadDisplayPreferences()
|
||||||
|
|||||||
Reference in New Issue
Block a user