fix: incremental loading for actions (#449)

* fix: incremental loading for actions

* fix: simplify loadingExtensions initialization

* fix: loading spinner per extension

* refactor: minor changes

* fix: loading and hidden state should not be persisted

* chore(deps): update snjs

* fix: keep === comparison operator

Co-authored-by: Johnny Almonte <johnny243@users.noreply.github.com>
This commit is contained in:
Johnny A
2020-08-26 09:35:54 -04:00
committed by GitHub
parent a63bfd8c0a
commit c4506a5407
3 changed files with 65 additions and 44 deletions

View File

@@ -2,7 +2,7 @@ import { WebApplication } from '@/ui_models/application';
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import template from '%/directives/actions-menu.pug'; import template from '%/directives/actions-menu.pug';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { SNItem, Action, SNActionsExtension } from 'snjs/dist/@types'; import { SNItem, Action, SNActionsExtension, UuidString } from 'snjs/dist/@types';
import { ActionResponse } from 'snjs/dist/@types/services/actions_service'; import { ActionResponse } from 'snjs/dist/@types/services/actions_service';
import { ActionsExtensionMutator } from 'snjs/dist/@types/models/app/extension'; import { ActionsExtensionMutator } from 'snjs/dist/@types/models/app/extension';
@@ -15,7 +15,7 @@ type ActionSubRow = {
onClick: () => void onClick: () => void
label: string label: string
subtitle: string subtitle: string
spinnerClass: string | undefined spinnerClass?: string
} }
type UpdateActionParams = { type UpdateActionParams = {
@@ -24,24 +24,21 @@ type UpdateActionParams = {
subrows?: ActionSubRow[] subrows?: ActionSubRow[]
} }
type UpdateExtensionParams = { type ActionsMenuState = {
hidden?: boolean extensions: SNActionsExtension[],
hiddenState: Record<UuidString, Boolean>
loadingState: Record<UuidString, Boolean>
} }
class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope { class ActionsMenuCtrl extends PureViewCtrl<{}, ActionsMenuState> implements ActionsMenuScope {
application!: WebApplication application!: WebApplication
item!: SNItem item!: SNItem
public loadingExtensions: boolean = true
/* @ngInject */ /* @ngInject */
constructor( constructor(
$timeout: ng.ITimeoutService $timeout: ng.ITimeoutService
) { ) {
super($timeout); super($timeout);
this.state = {
extensions: []
};
} }
$onInit() { $onInit() {
@@ -52,20 +49,28 @@ class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
this.loadExtensions(); this.loadExtensions();
}; };
async loadExtensions() { /** @override */
const actionExtensions = this.application.actionsManager!.getExtensions().sort((a, b) => { getInitialState() {
const extensions = this.application.actionsManager!.getExtensions().sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
}); });
const extensionsForItem = await Promise.all(actionExtensions.map((extension) => { return {
return this.application.actionsManager!.loadExtensionInContextOfItem( extensions,
loadingState: {},
hiddenState: {}
};
}
async loadExtensions() {
await Promise.all(this.state.extensions.map(async (extension: SNActionsExtension) => {
await this.setLoadingExtension(extension.uuid, true);
const updatedExtension = await this.application.actionsManager!.loadExtensionInContextOfItem(
extension, extension,
this.props.item this.item
); );
await this.updateExtension(updatedExtension!);
await this.setLoadingExtension(extension.uuid, false);
})); }));
this.loadingExtensions = false;
await this.setState({
extensions: extensionsForItem
});
} }
async executeAction(action: Action, extension: SNActionsExtension) { async executeAction(action: Action, extension: SNActionsExtension) {
@@ -79,7 +84,7 @@ class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
await this.updateAction(action, extension, { running: true }); await this.updateAction(action, extension, { running: true });
const response = await this.application.actionsManager!.runAction( const response = await this.application.actionsManager!.runAction(
action, action,
this.props.item, this.item,
async () => { async () => {
/** @todo */ /** @todo */
return ''; return '';
@@ -144,40 +149,59 @@ class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
await this.updateExtension(updatedExtension); await this.updateExtension(updatedExtension);
} }
private async updateExtension( private async updateExtension(extension: SNActionsExtension) {
extension: SNActionsExtension,
params?: UpdateExtensionParams
) {
const updatedExtension = await this.application.changeItem(extension.uuid, (mutator) => {
const extensionMutator = mutator as ActionsExtensionMutator;
extensionMutator.hidden = Boolean(params?.hidden);
}) as SNActionsExtension;
const extensions = this.state.extensions.map((ext: SNActionsExtension) => { const extensions = this.state.extensions.map((ext: SNActionsExtension) => {
if (extension.uuid === ext.uuid) { if (extension.uuid === ext.uuid) {
return updatedExtension; return extension;
} }
return ext; return ext;
}); });
await this.setState({ await this.setState({
extensions: extensions extensions
}); });
} }
private async reloadExtension(extension: SNActionsExtension) { private async reloadExtension(extension: SNActionsExtension) {
const extensionInContext = await this.application.actionsManager!.loadExtensionInContextOfItem( const extensionInContext = await this.application.actionsManager!.loadExtensionInContextOfItem(
extension, extension,
this.props.item this.item
); );
const extensions = this.state.extensions.map((ext: SNActionsExtension) => { const extensions = this.state.extensions.map((ext: SNActionsExtension) => {
if (extension.uuid === ext.uuid) { if (extension.uuid === ext.uuid) {
return extensionInContext; return extensionInContext!;
} }
return ext; return ext;
}); });
this.setState({ this.setState({
extensions: extensions extensions
}); });
} }
private async toggleExtensionVisibility(extensionUuid: UuidString) {
const { hiddenState } = this.state;
hiddenState[extensionUuid] = !hiddenState[extensionUuid] ?? false;
await this.setState({
hiddenState
});
}
private isExtensionVisible(extensionUuid: UuidString) {
const { hiddenState } = this.state;
return hiddenState[extensionUuid] ?? false;
}
private async setLoadingExtension(extensionUuid: UuidString, value = false) {
const { loadingState } = this.state;
loadingState[extensionUuid] = value;
await this.setState({
loadingState
});
}
private isExtensionLoading(extensionUuid: UuidString) {
const { loadingState } = this.state;
return loadingState[extensionUuid] ?? false;
}
} }
export class ActionsMenu extends WebDirective { export class ActionsMenu extends WebDirective {

View File

@@ -7,23 +7,20 @@
target='blank' target='blank'
) )
menu-row(label="'Download Actions'") menu-row(label="'Download Actions'")
div(ng-if='self.loadingExtensions')
.sk-menu-panel-header
.sk-menu-panel-column
.sk-menu-panel-header-title Loading...
.sk-spinner.small.loading
div(ng-repeat='extension in self.state.extensions track by extension.uuid') div(ng-repeat='extension in self.state.extensions track by extension.uuid')
.sk-menu-panel-header( .sk-menu-panel-header(
ng-click='self.updateExtension(extension, { hidden: !extension.hidden }); $event.stopPropagation();' ng-click='self.toggleExtensionVisibility(extension.uuid); $event.stopPropagation();'
) )
.sk-menu-panel-column .sk-menu-panel-column
.sk-menu-panel-header-title {{extension.name}} .sk-menu-panel-header-title {{extension.name}}
div(ng-if='extension.hidden') … div(ng-if='self.isExtensionVisible(extension.uuid)') …
div(ng-if='self.isExtensionLoading(extension.uuid)')
.sk-spinner.small.loading
menu-row( menu-row(
action='self.executeAction(action, extension)', action='self.executeAction(action, extension)',
label='action.label', label='action.label',
ng-if='!extension.hidden', ng-if='!self.isExtensionVisible(extension.uuid) && !self.isExtensionLoading(extension.uuid)',
ng-repeat='action in extension.actionsWithContextForItem(self.props.item) track by $index', ng-repeat='action in extension.actionsWithContextForItem(self.item) track by $index',
disabled='action.running' disabled='action.running'
spinner-class="action.running ? 'info' : null", spinner-class="action.running ? 'info' : null",
sub-rows='action.subrows', sub-rows='action.subrows',
@@ -36,5 +33,5 @@
menu-row( menu-row(
faded='true', faded='true',
label="'No Actions Available'", label="'No Actions Available'",
ng-if='extension.actionsWithContextForItem(self.props.item).length == 0' ng-if='extension.actionsWithContextForItem(self.item).length == 0'
) )

View File

@@ -64,6 +64,6 @@
}, },
"dependencies": { "dependencies": {
"sncrypto": "github:standardnotes/sncrypto#4a080efeb646dbf9ca3dffdfcfa9d081b4dc6de0", "sncrypto": "github:standardnotes/sncrypto#4a080efeb646dbf9ca3dffdfcfa9d081b4dc6de0",
"snjs": "github:standardnotes/snjs#75ac6ff8e620f3b7e796b117b9d0afce303962a0" "snjs": "github:standardnotes/snjs#2ef57eba1fc0e4f81e667d80bc3b99e0f650b6d1"
} }
} }