feat: component viewer (#781)

* wip: component viewer

* feat: get component status from component viewer

* fix: remove unused property

* chore(deps): snjs 2.29.0

* fix: import location
This commit is contained in:
Mo
2021-12-24 10:41:02 -06:00
committed by GitHub
parent 237cd91acd
commit ebdae31965
39 changed files with 874 additions and 1012 deletions

View File

@@ -77,6 +77,8 @@ export class AppState {
editingTag: SNTag | undefined;
_templateTag: SNTag | undefined;
private multiEditorSupport = false;
readonly quickSettingsMenu = new QuickSettingsState();
readonly accountMenu: AccountMenuState;
readonly actionsMenu = new ActionsMenuState();
@@ -224,27 +226,21 @@ export class AppState {
storage.set(StorageKey.ShowBetaWarning, true);
}
/**
* Creates a new editor if one doesn't exist. If one does, we'll replace the
* editor's note with an empty one.
*/
async createEditor(title?: string) {
const activeEditor = this.getActiveEditor();
if (!this.multiEditorSupport) {
this.closeActiveEditor();
}
const activeTagUuid = this.selectedTag
? this.selectedTag.isSmartTag
? undefined
: this.selectedTag.uuid
: undefined;
if (!activeEditor) {
this.application.editorGroup.createEditor(
undefined,
title,
activeTagUuid
);
} else {
await activeEditor.reset(title, activeTagUuid);
}
await this.application.editorGroup.createEditor(
undefined,
title,
activeTagUuid
);
}
getActiveEditor() {

View File

@@ -167,12 +167,12 @@ export class NotesState {
return;
}
if (!this.activeEditor) {
this.application.editorGroup.createEditor(noteUuid);
} else {
this.activeEditor.setNote(note);
if (this.activeEditor) {
this.application.editorGroup.closeActiveEditor();
}
await this.application.editorGroup.createEditor(noteUuid);
this.appState.noteTags.reloadTags();
await this.onActiveEditorChanged();

View File

@@ -1,16 +1,20 @@
import { ContentType, SNSmartTag, SNTag, UuidString } from '@standardnotes/snjs';
import {
ContentType,
SNSmartTag,
SNTag,
UuidString,
} from '@standardnotes/snjs';
import {
action,
computed,
makeAutoObservable,
makeObservable,
observable,
runInAction
runInAction,
} from 'mobx';
import { WebApplication } from '../application';
import { FeaturesState } from './features_state';
export class TagsState {
tags: SNTag[] = [];
smartTags: SNSmartTag[] = [];

View File

@@ -18,12 +18,9 @@ import {
PermissionDialog,
Platform,
SNApplication,
SNComponent,
} from '@standardnotes/snjs';
import angular from 'angular';
import { ComponentModalScope } from './../directives/views/componentModal';
import { AccountSwitcherScope, PermissionsModalScope } from './../types';
import { ComponentGroup } from './component_group';
type WebServices = {
appState: AppState;
@@ -40,7 +37,6 @@ export class WebApplication extends SNApplication {
private webServices!: WebServices;
private currentAuthenticationElement?: angular.IRootElementService;
public editorGroup: EditorGroup;
public componentGroup: ComponentGroup;
/* @ngInject */
constructor(
@@ -71,8 +67,6 @@ export class WebApplication extends SNApplication {
this.scope = scope;
deviceInterface.setApplication(this);
this.editorGroup = new EditorGroup(this);
this.componentGroup = new ComponentGroup(this);
this.openModalComponent = this.openModalComponent.bind(this);
this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this);
}
@@ -85,14 +79,12 @@ export class WebApplication extends SNApplication {
(service as any).application = undefined;
}
this.webServices = {} as WebServices;
(this.$compile as any) = undefined;
(this.$compile as unknown) = undefined;
this.editorGroup.deinit();
this.componentGroup.deinit();
(this.scope! as any).application = undefined;
(this.scope as any).application = undefined;
this.scope!.$destroy();
this.scope = undefined;
(this.openModalComponent as any) = undefined;
(this.presentPermissionsDialog as any) = undefined;
(this.presentPermissionsDialog as unknown) = undefined;
/** Allow our Angular directives to be destroyed and any pending digest cycles
* to complete before destroying the global application instance and all its services */
setTimeout(() => {
@@ -105,8 +97,7 @@ export class WebApplication extends SNApplication {
onStart(): void {
super.onStart();
this.componentManager!.openModalComponent = this.openModalComponent;
this.componentManager!.presentPermissionsDialog =
this.componentManager.presentPermissionsDialog =
this.presentPermissionsDialog;
}
@@ -210,24 +201,6 @@ export class WebApplication extends SNApplication {
this.applicationElement.append(el);
}
async openModalComponent(component: SNComponent): Promise<void> {
switch (component.package_info?.identifier) {
case 'org.standardnotes.cloudlink':
if (!(await this.authorizeCloudLinkAccess())) {
return;
}
break;
}
const scope = this.scope!.$new(true) as Partial<ComponentModalScope>;
scope.componentUuid = component.uuid;
scope.application = this;
const el = this.$compile!(
"<component-modal application='application' component-uuid='componentUuid' " +
"class='sk-modal'></component-modal>"
)(scope as any);
this.applicationElement.append(el);
}
presentPermissionsDialog(dialog: PermissionDialog) {
const scope = this.scope!.$new(true) as PermissionsModalScope;
scope.permissionsString = dialog.permissionsString;

View File

@@ -1,100 +0,0 @@
import { SNComponent, ComponentArea, removeFromArray, addIfUnique , UuidString } from '@standardnotes/snjs';
import { WebApplication } from './application';
/** Areas that only allow a single component to be active */
const SingleComponentAreas = [
ComponentArea.Editor,
ComponentArea.NoteTags,
ComponentArea.TagsList
];
export class ComponentGroup {
private application: WebApplication
changeObservers: any[] = []
activeComponents: UuidString[] = []
constructor(application: WebApplication) {
this.application = application;
}
get componentManager() {
return this.application.componentManager!;
}
public deinit() {
(this.application as any) = undefined;
}
async activateComponent(component: SNComponent) {
if (this.activeComponents.includes(component.uuid)) {
return;
}
if (SingleComponentAreas.includes(component.area)) {
const currentActive = this.activeComponentForArea(component.area);
if (currentActive) {
await this.deactivateComponent(currentActive, false);
}
}
addIfUnique(this.activeComponents, component.uuid);
await this.componentManager.activateComponent(component.uuid);
this.notifyObservers();
}
async deactivateComponent(component: SNComponent, notify = true) {
if (!this.activeComponents.includes(component.uuid)) {
return;
}
removeFromArray(this.activeComponents, component.uuid);
/** If this function is called as part of global application deinit (locking),
* componentManager can be destroyed. In this case, it's harmless to not take any
* action since the componentManager will be destroyed, and the component will
* essentially be deregistered. */
if(this.componentManager) {
await this.componentManager.deactivateComponent(component.uuid);
if(notify) {
this.notifyObservers();
}
}
}
async deactivateComponentForArea(area: ComponentArea) {
const component = this.activeComponentForArea(area);
if (component) {
return this.deactivateComponent(component);
}
}
activeComponentForArea(area: ComponentArea) {
return this.activeComponentsForArea(area)[0];
}
activeComponentsForArea(area: ComponentArea) {
return this.allActiveComponents().filter((c) => c.area === area);
}
allComponentsForArea(area: ComponentArea) {
return this.componentManager.componentsForArea(area);
}
private allActiveComponents() {
return this.application.getAll(this.activeComponents) as SNComponent[];
}
/**
* Notifies observer when the active editor has changed.
*/
public addChangeObserver(callback: () => void) {
this.changeObservers.push(callback);
callback();
return () => {
removeFromArray(this.changeObservers, callback);
};
}
private notifyObservers() {
for (const observer of this.changeObservers) {
observer();
}
}
}

View File

@@ -3,37 +3,36 @@ import {
ContentType,
PayloadSource,
UuidString,
TagMutator,
SNTag,
} from '@standardnotes/snjs';
import { WebApplication } from './application';
import { NoteTagsState } from './app_state/note_tags_state';
export class Editor {
public note!: SNNote;
private application: WebApplication;
private _onNoteChange?: () => void;
private _onNoteValueChange?: (note: SNNote, source?: PayloadSource) => void;
private onNoteValueChange?: (note: SNNote, source: PayloadSource) => void;
private removeStreamObserver?: () => void;
public isTemplateNote = false;
constructor(
application: WebApplication,
noteUuid: string | undefined,
noteTitle: string | undefined,
noteTag: UuidString | undefined
private defaultTitle: string | undefined,
private defaultTag: UuidString | undefined
) {
this.application = application;
if (noteUuid) {
this.note = application.findItem(noteUuid) as SNNote;
this.streamItems();
} else {
this.reset(noteTitle, noteTag)
.then(() => this.streamItems())
.catch(console.error);
}
}
async initialize(): Promise<void> {
if (!this.note) {
await this.createTemplateNote(this.defaultTitle, this.defaultTag);
}
this.streamItems();
}
private streamItems() {
this.removeStreamObserver = this.application.streamItems(
ContentType.Note,
@@ -45,14 +44,12 @@ export class Editor {
deinit() {
this.removeStreamObserver?.();
(this.removeStreamObserver as any) = undefined;
this._onNoteChange = undefined;
(this.application as any) = undefined;
this._onNoteChange = undefined;
this._onNoteValueChange = undefined;
(this.removeStreamObserver as unknown) = undefined;
(this.application as unknown) = undefined;
this.onNoteValueChange = undefined;
}
private handleNoteStream(notes: SNNote[], source?: PayloadSource) {
private handleNoteStream(notes: SNNote[], source: PayloadSource) {
/** Update our note object reference whenever it changes */
const matchingNote = notes.find((item) => {
return item.uuid === this.note.uuid;
@@ -60,7 +57,7 @@ export class Editor {
if (matchingNote) {
this.isTemplateNote = false;
this.note = matchingNote;
this._onNoteValueChange && this._onNoteValueChange!(matchingNote, source);
this.onNoteValueChange?.(matchingNote, source);
}
}
@@ -73,53 +70,31 @@ export class Editor {
* Reverts the editor to a blank state, removing any existing note from view,
* and creating a placeholder note.
*/
async reset(noteTitle = '', noteTag?: UuidString) {
async createTemplateNote(defaultTitle?: string, noteTag?: UuidString) {
const note = (await this.application.createTemplateItem(ContentType.Note, {
text: '',
title: noteTitle,
title: defaultTitle,
references: [],
})) as SNNote;
if (noteTag) {
const tag = this.application.findItem(noteTag) as SNTag;
await this.application.addTagHierarchyToNote(note, tag);
}
if (!this.isTemplateNote || this.note.title !== note.title) {
this.setNote(note as SNNote, true);
}
}
/**
* Register to be notified when the editor's note changes.
*/
public onNoteChange(callback: () => void) {
this._onNoteChange = callback;
if (this.note) {
callback();
}
}
public clearNoteChangeListener() {
this._onNoteChange = undefined;
this.isTemplateNote = true;
this.note = note;
this.onNoteValueChange?.(this.note, this.note.payload.source);
}
/**
* Register to be notified when the editor's note's values change
* (and thus a new object reference is created)
*/
public onNoteValueChange(
callback: (note: SNNote, source?: PayloadSource) => void
public setOnNoteValueChange(
callback: (note: SNNote, source: PayloadSource) => void
) {
this._onNoteValueChange = callback;
}
/**
* Sets the editor contents by setting its note.
*/
public setNote(note: SNNote, isTemplate = false) {
this.note = note;
this.isTemplateNote = isTemplate;
if (this._onNoteChange) {
this._onNoteChange();
this.onNoteValueChange = callback;
if (this.note) {
this.onNoteValueChange(this.note, this.note.payload.source);
}
}
}

View File

@@ -2,31 +2,31 @@ import { removeFromArray, UuidString } from '@standardnotes/snjs';
import { Editor } from './editor';
import { WebApplication } from './application';
type EditorGroupChangeCallback = () => void
type EditorGroupChangeCallback = () => void;
export class EditorGroup {
public editors: Editor[] = []
private application: WebApplication
changeObservers: EditorGroupChangeCallback[] = []
public editors: Editor[] = [];
private application: WebApplication;
changeObservers: EditorGroupChangeCallback[] = [];
constructor(application: WebApplication) {
this.application = application;
}
public deinit() {
(this.application as any) = undefined;
(this.application as unknown) = undefined;
for (const editor of this.editors) {
this.deleteEditor(editor);
}
}
createEditor(
async createEditor(
noteUuid?: string,
noteTitle?: string,
noteTag?: UuidString
) {
const editor = new Editor(this.application, noteUuid, noteTitle, noteTag);
await editor.initialize();
this.editors.push(editor);
this.notifyObservers();
}
@@ -43,13 +43,13 @@ export class EditorGroup {
closeActiveEditor() {
const activeEditor = this.activeEditor;
if(activeEditor) {
if (activeEditor) {
this.deleteEditor(activeEditor);
}
}
closeAllEditors() {
for(const editor of this.editors) {
for (const editor of this.editors) {
this.deleteEditor(editor);
}
}