Component management improvements, removal of dependence on noteReady flag

This commit is contained in:
Mo Bitar
2020-04-15 10:26:55 -05:00
parent a2303aa7af
commit 0d44a2ff64
17 changed files with 1618 additions and 1119 deletions

View File

@@ -1,5 +1,5 @@
import { WebApplication } from '@/ui_models/application';
import { SNComponent } from 'snjs';
import { SNComponent, ComponentAction, LiveItem } from 'snjs';
import { WebDirective } from './../../types';
import template from '%/directives/component-view.pug';
import { isDesktopApplication } from '../../utils';
@@ -7,38 +7,37 @@ import { isDesktopApplication } from '../../utils';
* The maximum amount of time we'll wait for a component
* to load before displaying error
*/
const MAX_LOAD_THRESHOLD = 4000;
const VISIBILITY_CHANGE_LISTENER_KEY = 'visibilitychange';
const MaxLoadThreshold = 4000;
const VisibilityChangeKey = 'visibilitychange';
interface ComponentViewScope {
component: SNComponent
componentUuid: string
onLoad?: (component: SNComponent) => void
manualDealloc: boolean
application: WebApplication
}
class ComponentViewCtrl implements ComponentViewScope {
$rootScope: ng.IRootScopeService
$timeout: ng.ITimeoutService
componentValid = true
cleanUpOn: () => void
unregisterComponentHandler!: () => void
component!: SNComponent
/** @scope */
onLoad?: (component: SNComponent) => void
manualDealloc = false
componentUuid!: string
application!: WebApplication
unregisterDesktopObserver!: () => void
didRegisterObservers = false
lastComponentValue?: SNComponent
issueLoading = false
reloading = false
expired = false
loading = false
didAttemptReload = false
error: 'offline-restricted' | 'url-missing' | undefined
loadTimeout: any
liveComponent!: LiveItem<SNComponent>
private $rootScope: ng.IRootScopeService
private $timeout: ng.ITimeoutService
private componentValid = true
private cleanUpOn: () => void
private unregisterComponentHandler!: () => void
private unregisterDesktopObserver!: () => void
private didRegisterObservers = false
private issueLoading = false
public reloading = false
private expired = false
private loading = false
private didAttemptReload = false
public error: 'offline-restricted' | 'url-missing' | undefined
private loadTimeout: any
/* @ngInject */
constructor(
@@ -51,7 +50,6 @@ class ComponentViewCtrl implements ComponentViewScope {
this.cleanUpOn = $scope.$on('ext-reload-complete', () => {
this.reloadStatus(false);
});
/** To allow for registering events */
this.onVisibilityChange = this.onVisibilityChange.bind(this);
}
@@ -61,49 +59,56 @@ class ComponentViewCtrl implements ComponentViewScope {
(this.cleanUpOn as any) = undefined;
this.unregisterComponentHandler();
(this.unregisterComponentHandler as any) = undefined;
if (this.component && !this.manualDealloc) {
/* application and componentManager may be destroyed if this onDestroy is part of
the entire application being destroyed rather than part of just a single component
view being removed */
if (this.application && this.application.componentManager) {
this.application.componentManager.deregisterComponent(this.component);
}
}
this.unregisterDesktopObserver();
(this.unregisterDesktopObserver as any) = undefined;
document.removeEventListener(
VISIBILITY_CHANGE_LISTENER_KEY,
this.onVisibilityChange
);
(this.component as any) = undefined;
this.onLoad = undefined;
this.liveComponent.deinit();
(this.liveComponent as any) = undefined;
(this.application as any) = undefined;
(this.onVisibilityChange as any) = undefined;
this.onLoad = undefined;
document.removeEventListener(
VisibilityChangeKey,
this.onVisibilityChange
);
}
$onChanges() {
if (!this.didRegisterObservers) {
this.didRegisterObservers = true;
this.registerComponentHandlers();
this.registerPackageUpdateObserver();
}
const newComponent = this.component;
const oldComponent = this.lastComponentValue;
this.lastComponentValue = newComponent;
if (oldComponent && oldComponent !== newComponent) {
this.application.componentManager!.deregisterComponent(
oldComponent
);
}
if (newComponent && newComponent !== oldComponent) {
this.application.componentManager!.registerComponent(
newComponent
)
this.reloadStatus();
}
$onInit() {
this.liveComponent = new LiveItem(this.componentUuid, this.application);
this.loadComponent();
this.registerComponentHandlers();
this.registerPackageUpdateObserver();
}
registerPackageUpdateObserver() {
get component() {
return this.liveComponent?.item;
}
private loadComponent() {
if (!this.component) {
throw 'Component view is missing component';
}
if (!this.component.active) {
throw 'Component view component must be active';
}
const iframe = this.application.componentManager!.iframeForComponent(
this.component
);
if (!iframe) {
return;
}
this.loading = true;
if (this.loadTimeout) {
this.$timeout.cancel(this.loadTimeout);
}
this.loadTimeout = this.$timeout(() => {
this.handleIframeLoadTimeout();
}, MaxLoadThreshold);
iframe.onload = (event) => {
this.handleIframeLoad(iframe);
};
}
private registerPackageUpdateObserver() {
this.unregisterDesktopObserver = this.application.getDesktopService()
.registerUpdateObserver((component: SNComponent) => {
if (component === this.component && component.active) {
@@ -112,27 +117,19 @@ class ComponentViewCtrl implements ComponentViewScope {
});
}
registerComponentHandlers() {
private registerComponentHandlers() {
this.unregisterComponentHandler = this.application.componentManager!.registerHandler({
identifier: 'component-view-' + Math.random(),
areas: [this.component.area],
activationHandler: (component) => {
if (component !== this.component) {
return;
}
this.$timeout(() => {
this.handleActivation();
});
},
actionHandler: (component, action, data) => {
if (action === 'set-size') {
if (action === ComponentAction.SetSize) {
this.application.componentManager!.handleSetSizeEvent(component, data);
}
}
});
}
onVisibilityChange() {
private onVisibilityChange() {
if (document.visibilityState === 'hidden') {
return;
}
@@ -141,13 +138,13 @@ class ComponentViewCtrl implements ComponentViewScope {
}
}
async reloadComponent() {
public async reloadComponent() {
this.componentValid = false;
await this.application.componentManager!.reloadComponent(this.component);
this.reloadStatus();
}
reloadStatus(doManualReload = true) {
public reloadStatus(doManualReload = true) {
this.reloading = true;
const component = this.component;
const previouslyValid = this.componentValid;
@@ -183,36 +180,12 @@ class ComponentViewCtrl implements ComponentViewScope {
if (this.expired && doManualReload) {
this.$rootScope.$broadcast('reload-ext-dat');
}
this.$timeout(() => {
this.reloading = false;
}, 500);
}
handleActivation() {
if (!this.component || !this.component.active) {
return;
}
const iframe = this.application.componentManager!.iframeForComponent(
this.component
);
if (!iframe) {
return;
}
this.loading = true;
if (this.loadTimeout) {
this.$timeout.cancel(this.loadTimeout);
}
this.loadTimeout = this.$timeout(() => {
this.handleIframeLoadTimeout();
}, MAX_LOAD_THRESHOLD);
iframe.onload = (event) => {
this.handleIframeLoad(iframe);
};
}
async handleIframeLoadTimeout() {
private async handleIframeLoadTimeout() {
if (this.loading) {
this.loading = false;
this.issueLoading = true;
@@ -221,14 +194,14 @@ class ComponentViewCtrl implements ComponentViewScope {
this.reloadComponent();
} else {
document.addEventListener(
VISIBILITY_CHANGE_LISTENER_KEY,
VisibilityChangeKey,
this.onVisibilityChange
);
}
}
}
async handleIframeLoad(iframe: HTMLIFrameElement) {
private async handleIframeLoad(iframe: HTMLIFrameElement) {
let desktopError = false;
if (isDesktopApplication()) {
try {
@@ -252,11 +225,7 @@ class ComponentViewCtrl implements ComponentViewScope {
}, avoidFlickerTimeout);
}
disableActiveTheme() {
this.application.getThemeService().deactivateAllThemes();
}
getUrl() {
public getUrl() {
const url = this.application.componentManager!.urlForComponent(this.component);
return url;
}
@@ -268,9 +237,8 @@ export class ComponentView extends WebDirective {
this.restrict = 'E';
this.template = template;
this.scope = {
component: '=',
componentUuid: '=',
onLoad: '=?',
manualDealloc: '=?',
application: '='
};
this.controller = ComponentViewCtrl;

View File

@@ -15,7 +15,7 @@ interface EditorMenuScope {
class EditorMenuCtrl extends PureViewCtrl implements EditorMenuScope {
callback!: (component: SNComponent) => void
callback!: () => (component: SNComponent) => void
selectedEditor!: SNComponent
currentItem!: SNItem
application!: WebApplication
@@ -52,7 +52,7 @@ class EditorMenuCtrl extends PureViewCtrl implements EditorMenuScope {
}
}
this.$timeout(() => {
this.callback(component);
this.callback()(component);
});
}

View File

@@ -1,3 +1,4 @@
import { ComponentGroup } from './component_group';
import { EditorGroup } from '@/ui_models/editor_group';
import { InputModalScope } from '@/directives/views/inputModal';
import { PasswordWizardType, PasswordWizardScope } from '@/types';
@@ -13,7 +14,7 @@ import {
import angular from 'angular';
import { getPlatformString } from '@/utils';
import { AlertService } from '@/services/alertService';
import { WebDeviceInterface } from '@/web_device_interface';
import { WebDeviceInterface } from '@/interface';
import {
AppState,
DesktopManager,
@@ -46,6 +47,7 @@ export class WebApplication extends SNApplication {
private webServices!: WebServices
private currentAuthenticationElement?: JQLite
public editorGroup: EditorGroup
public componentGroup: ComponentGroup
/* @ngInject */
constructor(
@@ -74,6 +76,7 @@ export class WebApplication extends SNApplication {
this.onDeinit = onDeinit;
deviceInterface.setApplication(this);
this.editorGroup = new EditorGroup(this);
this.componentGroup = new ComponentGroup(this);
}
/** @override */
@@ -90,6 +93,7 @@ export class WebApplication extends SNApplication {
this.onDeinit = undefined;
this.$compile = undefined;
this.editorGroup.deinit();
this.componentGroup.deinit();
(this.scope! as any).application = undefined;
this.scope!.$destroy();
this.scope = undefined;

View File

@@ -0,0 +1,100 @@
import { dictToArray } from '../utils';
import { SNComponent, ComponentArea, removeFromArray } from '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: Partial<Record<string, SNComponent>> = {}
constructor(application: WebApplication) {
this.application = application;
}
get componentManager() {
return this.application.componentManager!;
}
public deinit() {
(this.application as any) = undefined;
for (const component of this.allActiveComponents()) {
this.componentManager.deregisterComponent(component);
}
}
async activateComponent(component: SNComponent) {
if (this.activeComponents[component.uuid]) {
return;
}
if (SingleComponentAreas.includes(component.area)) {
const currentActive = this.activeComponentForArea(component.area);
if (currentActive) {
await this.deactivateComponent(currentActive, false);
}
}
this.activeComponents[component.uuid] = component;
await this.componentManager.activateComponent(component);
this.notifyObservers();
}
async deactivateComponent(component: SNComponent, notify = true) {
if (!this.activeComponents[component.uuid]) {
return;
}
delete this.activeComponents[component.uuid];
await this.componentManager.deactivateComponent(component);
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) {
const all = dictToArray(this.activeComponents);
return all.filter((c) => c.area === area);
}
allComponentsForArea(area: ComponentArea) {
return this.componentManager.componentsForArea(area);
}
private allActiveComponents() {
return dictToArray(this.activeComponents);
}
/**
* Notifies observer when the active editor has changed.
*/
public addChangeObserver(callback: () => void) {
this.changeObservers.push(callback);
return () => {
removeFromArray(this.changeObservers, callback);
}
}
private notifyObservers() {
for (const observer of this.changeObservers) {
observer();
}
}
}

View File

@@ -11,8 +11,8 @@ export function isNullOrUndefined(value: any) {
return value === null || value === undefined;
}
export function dictToArray(dict: any) {
return Object.keys(dict).map((key) => dict[key]);
export function dictToArray<T>(dict: Record<any, T>) {
return Object.keys(dict).map((key) => dict[key]!);
}
export function getPlatformString() {

View File

@@ -31,9 +31,9 @@
) {{self.state.noteStatus.message}}
.desc(ng-show='self.state.noteStatus.desc') {{self.state.noteStatus.desc}}
.editor-tags
#note-tags-component-container(ng-if='self.state.tagsComponent')
#note-tags-component-container(ng-if='self.activeTagsComponent')
component-view.component-view(
component='self.state.tagsComponent',
component-uuid='self.activeTagsComponent.uuid',
ng-class="{'locked' : self.noteLocked}",
ng-style="self.noteLocked && {'pointer-events' : 'none'}",
application='self.application'
@@ -41,7 +41,7 @@
input.tags-input(
ng-blur='self.onTagsInputBlur()',
ng-disabled='self.noteLocked',
ng-if='!(self.state.tagsComponent && self.state.tagsComponent.active)',
ng-if='!self.activeTagsComponent',
ng-keyup='$event.keyCode == 13 && $event.target.blur();',
ng-model='self.editorValues.tagsInputValue',
placeholder='#tags',
@@ -133,18 +133,18 @@
action="self.selectedMenuItem(true); self.toggleWebPrefKey(self.prefKeyMonospace)",
circle="self.state.monospaceEnabled ? 'success' : 'neutral'",
desc="'Toggles the font style for the default editor'",
disabled='self.state.selectedEditor',
disabled='self.activeEditorComponent',
label="'Monospace Font'",
subtitle="self.state.selectedEditor ? 'Not available with editor extensions' : null"
subtitle="self.activeEditorComponent ? 'Not available with editor extensions' : null"
)
menu-row(
action="self.selectedMenuItem(true); self.toggleWebPrefKey(self.prefKeySpellcheck)",
circle="self.state.spellcheck ? 'success' : 'neutral'",
desc="'Toggles spellcheck for the default editor'",
disabled='self.state.selectedEditor',
disabled='self.activeEditorComponent',
label="'Spellcheck'",
subtitle=`
self.state.selectedEditor
self.activeEditorComponent
? 'Not available with editor extensions'
: (self.state.isDesktop ? 'May degrade editor performance' : null)
`)
@@ -163,10 +163,10 @@
)
.sk-label Editor
editor-menu(
callback='self.editorMenuOnSelect()',
callback='self.editorMenuOnSelect',
current-item='self.note',
ng-if='self.state.showEditorMenu',
selected-editor='self.state.selectedEditor',
selected-editor='self.activeEditorComponent',
application='self.application'
)
.sk-app-bar-item(
@@ -205,8 +205,8 @@
property="'left'"
)
component-view.component-view(
component='self.state.selectedEditor',
ng-if='self.state.selectedEditor',
component-uuid='self.activeEditorComponent.uuid',
ng-if='self.activeEditorComponent',
on-load='self.onEditorLoad',
application='self.application'
)
@@ -216,7 +216,7 @@
ng-change='self.contentChanged()',
ng-click='self.clickedTextArea()',
ng-focus='self.onContentFocus()',
ng-if='!self.state.selectedEditor',
ng-if='!self.activeEditorComponent',
ng-model='self.editorValues.text',
ng-model-options='{ debounce: self.state.editorDebounce}',
ng-readonly='self.noteLocked',
@@ -236,11 +236,11 @@
| There was an error decrypting this item. Ensure you are running the
| latest version of this app, then sign out and sign back in to try again.
#editor-pane-component-stack(ng-show='self.note')
#component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.componentStack.length')
#component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.allStackComponents.length')
.left
.sk-app-bar-item(
ng-repeat='component in self.state.allStackComponents track by component.uuid'
ng-click='self.toggleStackComponentForCurrentItem(component)',
ng-repeat='component in self.state.componentStack track by component.uuid'
)
.sk-app-bar-item-column
.sk-circle.small(
@@ -250,10 +250,9 @@
.sk-label {{component.name}}
.sn-component
component-view.component-view.component-stack-item(
component='component',
ng-repeat='component in self.activeStackComponents track by component.uuid',
component-uuid='component.uuid',
manual-dealloc='true',
ng-if='component.active',
ng-repeat='component in self.state.componentStack track by component.uuid',
ng-show='!component.hidden',
application='self.application'
)

View File

@@ -58,15 +58,16 @@ type NoteStatus = {
}
type EditorState = {
allStackComponents: SNComponent[]
activeEditorComponent?: SNComponent
activeTagsComponent?: SNComponent
activeStackComponents: SNComponent[]
saveError?: any
editorComponent?: SNComponent
noteStatus?: NoteStatus
tagsAsStrings?: string
marginResizersEnabled?: boolean
monospaceEnabled?: boolean
isDesktop?: boolean
tagsComponent?: SNComponent
componentStack?: SNComponent[]
syncTakingTooLong: boolean
showExtensions: boolean
noteReady: boolean
@@ -106,6 +107,7 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
private removeTrashKeyObserver?: any
private removeDeleteKeyObserver?: any
private removeTabObserver?: any
private removeComponentObserver: any
prefKeyMonospace: string
prefKeySpellcheck: string
@@ -133,6 +135,8 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
}
deinit() {
this.removeComponentObserver();
this.removeComponentObserver = undefined;
this.removeAltKeyObserver();
this.removeAltKeyObserver = undefined;
this.removeTrashKeyObserver();
@@ -173,20 +177,29 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
this.editorValues.text = note.text;
this.reloadTagsString();
}
});
this.removeComponentObserver = this.componentGroup.addChangeObserver(() => {
this.setEditorState({
activeEditorComponent: this.componentGroup.activeComponentForArea(ComponentArea.Editor),
activeTagsComponent: this.componentGroup.activeComponentForArea(ComponentArea.NoteTags),
activeStackComponents: this.componentGroup.activeComponentsForArea(ComponentArea.EditorStack)
})
})
}
/** @override */
getInitialState() {
return {
componentStack: [],
allStackComponents: [],
activeStackComponents: [],
editorDebounce: EDITOR_DEBOUNCE,
isDesktop: isDesktopApplication(),
spellcheck: true,
noteReady: true,
mutable: {
tagsString: ''
}
};
} as Partial<EditorState>;
}
async setEditorState(state: Partial<EditorState>) {
@@ -238,6 +251,22 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
}
}
get activeEditorComponent() {
return this.getState().activeEditorComponent;
}
get activeTagsComponent() {
return this.getState().activeTagsComponent;
}
get activeStackComponents() {
return this.getState().activeStackComponents;
}
get componentGroup() {
return this.application.componentGroup;
}
async handleEditorNoteChange() {
const note = this.editor.note;
await this.setEditorState({
@@ -248,31 +277,7 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
});
this.editorValues.title = note.title;
this.editorValues.text = note.text;
if (!note) {
this.setEditorState({
noteReady: false
});
return;
}
const associatedEditor = this.editorForNote(note);
if (associatedEditor && associatedEditor !== this.getState().editorComponent) {
/**
* Setting note to not ready will remove the editor from view in a flash,
* so we only want to do this if switching between external editors
*/
this.setEditorState({
noteReady: false,
editorComponent: associatedEditor
});
} else if (!associatedEditor) {
/** No editor */
this.setEditorState({
editorComponent: undefined
});
}
await this.setEditorState({
noteReady: true,
});
await this.reloadComponentEditorState();
this.reloadTagsString();
this.reloadPreferences();
if (note.safeText().length === 0) {
@@ -281,6 +286,19 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
this.reloadComponentContext();
}
async reloadComponentEditorState() {
const associatedEditor = this.application.componentManager!.editorForNote(this.note)
if (associatedEditor && associatedEditor !== this.activeEditorComponent) {
/** Setting note to not ready will remove the editor from view in a flash,
* so we only want to do this if switching between external editors */
await this.componentGroup.activateComponent(associatedEditor);
} else if (!associatedEditor) {
/** No editor */
await this.componentGroup.deactivateComponentForArea(ComponentArea.Editor);
}
return associatedEditor;
}
/**
* Because note.locked accesses note.content.appData,
@@ -334,10 +352,7 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
return;
}
/** Find the most recent editor for note */
const editor = this.editorForNote(this.note);
this.setEditorState({
editorComponent: editor
});
const editor = this.reloadComponentEditorState();
if (!editor) {
this.reloadFont();
}
@@ -345,10 +360,6 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
);
}
editorForNote(note: SNNote) {
return this.application.componentManager!.editorForNote(note);
}
setMenuState(menu: string, state: boolean) {
this.setEditorState({
[menu]: state
@@ -376,42 +387,39 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
this.setEditorState(menuState);
}
editorMenuOnSelect(component: SNComponent) {
if (!component || component.area === 'editor-editor') {
/** If plain editor or other editor */
this.setMenuState('showEditorMenu', false);
const editor = component;
if (this.getState().editorComponent && editor !== this.getState().editorComponent) {
this.disassociateComponentWithCurrentNote(this.getState().editorComponent!);
async editorMenuOnSelect(component?: SNComponent) {
this.setMenuState('showEditorMenu', false);
if (!component) {
if (!this.note.prefersPlainEditor) {
await this.application.changeItem(this.note.uuid, (mutator) => {
const noteMutator = mutator as NoteMutator;
noteMutator.prefersPlainEditor = true;
})
}
const note = this.note;
if (editor) {
const prefersPlain = note.prefersPlainEditor;
if (prefersPlain) {
this.application.changeItem(note.uuid, (mutator) => {
const noteMutator = mutator as NoteMutator;
noteMutator.prefersPlainEditor = false;
})
}
this.associateComponentWithCurrentNote(editor);
} else {
/** Note prefers plain editor */
if (!note.prefersPlainEditor) {
this.application.changeItem(note.uuid, (mutator) => {
const noteMutator = mutator as NoteMutator;
noteMutator.prefersPlainEditor = true;
})
}
this.reloadFont();
if(this.activeEditorComponent?.isExplicitlyEnabledForItem(this.note)) {
await this.disassociateComponentWithCurrentNote(this.activeEditorComponent);
}
this.setEditorState({
editorComponent: editor
});
} else if (component.area === 'editor-stack') {
this.toggleStackComponentForCurrentItem(component);
await this.componentGroup.deactivateComponentForArea(ComponentArea.Editor);
this.reloadFont();
}
else if (component.area === ComponentArea.Editor) {
const currentEditor = this.activeEditorComponent;
if (currentEditor && component !== currentEditor) {
await this.disassociateComponentWithCurrentNote(currentEditor);
}
const prefersPlain = this.note.prefersPlainEditor;
if (prefersPlain) {
await this.application.changeItem(this.note.uuid, (mutator) => {
const noteMutator = mutator as NoteMutator;
noteMutator.prefersPlainEditor = false;
})
}
await this.associateComponentWithCurrentNote(component);
await this.componentGroup.activateComponent(component);
}
else if (component.area === ComponentArea.EditorStack) {
await this.toggleStackComponentForCurrentItem(component);
}
/** Dirtying can happen above */
this.application.sync();
}
@@ -980,55 +988,16 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
ComponentArea.Editor
],
activationHandler: (component) => {
if (component.area === 'note-tags') {
this.setEditorState({
tagsComponent: component.active ? component : undefined
});
} else if (component.area === 'editor-editor') {
if (
component === this.getState().editorComponent &&
!component.active
) {
this.setEditorState({ editorComponent: undefined });
}
else if (this.getState().editorComponent) {
if (this.getState().editorComponent!.active && this.note) {
if (
component.isExplicitlyEnabledForItem(this.note)
&& !this.getState().editorComponent!.isExplicitlyEnabledForItem(this.note)
) {
this.setEditorState({ editorComponent: component });
}
}
}
else if (this.note) {
const enableable = (
component.isExplicitlyEnabledForItem(this.note)
|| component.isDefaultEditor()
);
if (
component.active
&& enableable
) {
this.setEditorState({ editorComponent: component });
} else {
/**
* Not a candidate, and no qualified editor.
* Disable the current editor.
*/
this.setEditorState({ editorComponent: undefined });
}
}
} else if (component.area === 'editor-stack') {
if (component.area === ComponentArea.EditorStack) {
this.reloadComponentContext();
}
},
contextRequestHandler: (component) => {
const currentEditor = this.activeEditorComponent;
if (
component === this.getState().editorComponent ||
component === this.getState().tagsComponent ||
this.getState().componentStack!.includes(component)
component === currentEditor ||
component === this.activeTagsComponent ||
this.activeStackComponents.includes(component)
) {
return this.note;
}
@@ -1040,7 +1009,10 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
},
actionHandler: (component, action, data) => {
if (action === ComponentAction.SetSize) {
const setSize = function (element: HTMLElement, size: { width: number, height: number }) {
const setSize = (
element: HTMLElement,
size: { width: number, height: number }
) => {
const widthString = typeof size.width === 'string'
? size.width
: `${data.width}px`;
@@ -1089,16 +1061,15 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
.sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
this.setEditorState({
componentStack: components
allStackComponents: components
});
}
reloadComponentContext() {
this.reloadComponentStackArray();
if (this.note) {
for (const component of this.getState().componentStack!) {
for (const component of this.getState().allStackComponents!) {
if (component.active) {
this.application.componentManager!.setComponentHidden(
component,
@@ -1113,33 +1084,34 @@ class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
this.application.componentManager!.contextItemDidChangeInArea(ComponentArea.Editor);
}
toggleStackComponentForCurrentItem(component: SNComponent) {
async toggleStackComponentForCurrentItem(component: SNComponent) {
const hidden = this.application.componentManager!.isComponentHidden(component);
if (hidden || !component.active) {
this.application.componentManager!.setComponentHidden(component, false);
this.associateComponentWithCurrentNote(component);
await this.associateComponentWithCurrentNote(component);
if (!component.active) {
this.application.componentManager!.activateComponent(component);
}
this.application.componentManager!.contextItemDidChangeInArea(ComponentArea.EditorStack);
} else {
this.application.componentManager!.setComponentHidden(component, true);
this.disassociateComponentWithCurrentNote(component);
await this.disassociateComponentWithCurrentNote(component);
}
this.application.sync();
}
disassociateComponentWithCurrentNote(component: SNComponent) {
async disassociateComponentWithCurrentNote(component: SNComponent) {
const note = this.note;
this.application.changeAndSaveItem(component.uuid, (m) => {
return this.application.changeItem(component.uuid, (m) => {
const mutator = m as ComponentMutator;
mutator.removeAssociatedItemId(note.uuid);
mutator.disassociateWithItem(note);
})
}
associateComponentWithCurrentNote(component: SNComponent) {
async associateComponentWithCurrentNote(component: SNComponent) {
const note = this.note;
this.application.changeAndSaveItem(component.uuid, (m) => {
return this.application.changeItem(component.uuid, (m) => {
const mutator = m as ComponentMutator;
mutator.removeDisassociatedItemId(note.uuid);
mutator.associateWithItem(note);

View File

@@ -1,7 +1,7 @@
#tags-column.sn-component.section.tags(aria-label='Tags')
.component-view-container(ng-if='self.component.active')
component-view.component-view(
component='self.component',
component-uuid='self.component.uuid',
application='self.application'
)
#tags-content.content(ng-if='!(self.component && self.component.active)')

View File

@@ -11,6 +11,6 @@
| {{ctrl.component.name}}
a.sk-a.info.close-button(ng-click="ctrl.dismiss()") Close
component-view.component-view(
component="ctrl.component",
component-uuid="ctrl.component.uuid",
application='ctrl.application'
)

View File

@@ -22,7 +22,7 @@
style="white-space: pre-wrap; font-size: 16px;"
) {{ctrl.content.text}}
component-view.component-view(
component="ctrl.editor"
component-uuid="ctrl.editor.uuid"
ng-if="ctrl.editor",
application='ctrl.application'
)

View File

@@ -0,0 +1,29 @@
import { DeviceInterface, SNApplication } from 'snjs';
export declare class WebDeviceInterface extends DeviceInterface {
private database;
constructor(namespace: string, timeout: any);
setApplication(application: SNApplication): void;
deinit(): void;
getRawStorageValue(key: string): Promise<string | null>;
getAllRawStorageKeyValues(): Promise<{
key: string;
value: any;
}[]>;
setRawStorageValue(key: string, value: any): Promise<void>;
removeRawStorageValue(key: string): Promise<void>;
removeAllRawStorageValues(): Promise<void>;
openDatabase(): Promise<{
isNewDatabase?: boolean | undefined;
} | undefined>;
private getDatabaseKeyPrefix;
private keyForPayloadId;
getAllRawDatabasePayloads(): Promise<any[]>;
saveRawDatabasePayload(payload: any): Promise<void>;
saveRawDatabasePayloads(payloads: any[]): Promise<void>;
removeRawDatabasePayloadWithId(id: string): Promise<void>;
removeAllRawDatabasePayloads(): Promise<void>;
getKeychainValue(): Promise<any>;
setKeychainValue(value: any): Promise<void>;
clearKeychainValue(): Promise<void>;
openUrl(url: string): void;
}

View File

@@ -1,4 +1,5 @@
/// <reference types="angular" />
import { ComponentGroup } from './component_group';
import { EditorGroup } from '@/ui_models/editor_group';
import { PasswordWizardType } from '@/types';
import { SNApplication, Challenge, ChallengeOrchestrator, ProtectedAction } from 'snjs';
@@ -21,27 +22,19 @@ export declare class WebApplication extends SNApplication {
private webServices;
private currentAuthenticationElement?;
editorGroup: EditorGroup;
componentGroup: ComponentGroup;
constructor($compile: ng.ICompileService, $timeout: ng.ITimeoutService, scope: ng.IScope, onDeinit: (app: WebApplication) => void);
/** @override */
deinit(): void;
setWebServices(services: WebServices): void;
/** @access public */
getAppState(): AppState;
/** @access public */
getDesktopService(): DesktopManager;
/** @access public */
getLockService(): LockManager;
/** @access public */
getArchiveService(): ArchiveManager;
/** @access public */
getNativeExtService(): NativeExtManager;
/** @access public */
getStatusService(): StatusManager;
/** @access public */
getThemeService(): ThemeManager;
/** @access public */
getPrefsService(): PreferencesManager;
/** @access public */
getKeyboardService(): KeyboardManager;
checkForSecurityUpdate(): Promise<boolean>;
presentPasswordWizard(type: PasswordWizardType): void;

View File

@@ -0,0 +1,23 @@
import { SNComponent, ComponentArea } from 'snjs';
import { WebApplication } from './application';
export declare class ComponentGroup {
private application;
changeObservers: any[];
activeComponents: Partial<Record<string, SNComponent>>;
constructor(application: WebApplication);
get componentManager(): import("../../../../../snjs/dist/@types").SNComponentManager;
deinit(): void;
registerComponentHandler(): void;
activateComponent(component: SNComponent): Promise<void>;
deactivateComponent(component: SNComponent): Promise<void>;
deactivateComponentForArea(area: ComponentArea): Promise<void>;
activeComponentForArea(area: ComponentArea): SNComponent;
activeComponentsForArea(area: ComponentArea): SNComponent[];
allComponentsForArea(area: ComponentArea): SNComponent[];
private allActiveComponents;
/**
* Notifies observer when the active editor has changed.
*/
addChangeObserver(callback: any): void;
private notifyObservers;
}

View File

@@ -1,6 +1,6 @@
export declare function getParameterByName(name: string, url: string): string | null;
export declare function isNullOrUndefined(value: any): boolean;
export declare function dictToArray(dict: any): any[];
export declare function dictToArray<T>(dict: Record<any, T>): NonNullable<T>[];
export declare function getPlatformString(): string;
export declare function dateToLocalizedString(date: Date): string;
/** Via https://davidwalsh.name/javascript-debounce-function */

2113
dist/javascripts/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long