Merge pull request #417 from standardnotes/004-actions

refactor: actions menu
This commit is contained in:
Johnny A
2020-06-29 22:26:28 -04:00
committed by GitHub
2 changed files with 106 additions and 31 deletions

View File

@@ -2,19 +2,37 @@ 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 '@node_modules/snjs/dist/@types'; import { SNItem, Action, SNActionsExtension } from 'snjs/dist/@types';
import { ActionResponse } from '@node_modules/snjs/dist/@types/services/actions_service'; import { ActionResponse } from 'snjs/dist/@types/services/actions_service';
import { ActionsExtensionMutator } from 'snjs/dist/@types/models/app/extension';
type ActionsMenuScope = { type ActionsMenuScope = {
application: WebApplication application: WebApplication
item: SNItem item: SNItem
} }
type ActionSubRow = {
onClick: () => void
label: string
subtitle: string
spinnerClass: string | undefined
}
type UpdateActionParams = {
running?: boolean
error?: boolean
subrows?: ActionSubRow[]
}
type UpdateExtensionParams = {
hidden?: boolean
}
class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope { class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
application!: WebApplication application!: WebApplication
item!: SNItem item!: SNItem
public loadingState: Partial<Record<string, boolean>> = {} public loadingExtensions: boolean = true
/* @ngInject */ /* @ngInject */
constructor( constructor(
@@ -35,32 +53,32 @@ class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
}; };
async loadExtensions() { async loadExtensions() {
const extensions = this.application.actionsManager!.getExtensions().sort((a, b) => { const actionExtensions = 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;
}); });
for (const extension of extensions) { const extensionsForItem = await Promise.all(actionExtensions.map((extension) => {
this.loadingState[extension.uuid] = true; return this.application.actionsManager!.loadExtensionInContextOfItem(
await this.application.actionsManager!.loadExtensionInContextOfItem(
extension, extension,
this.props.item this.props.item
); );
this.loadingState[extension.uuid] = false; }));
if (extensionsForItem.length == 0) {
this.loadingExtensions = false;
} }
this.setState({ await this.setState({
extensions: extensions extensions: extensionsForItem
}); });
} }
async executeAction(action: Action, extension: SNActionsExtension) { async executeAction(action: Action, extension: SNActionsExtension) {
if (action.verb === 'nested') { if (action.verb === 'nested') {
if (!action.subrows) { if (!action.subrows) {
action.subrows = this.subRowsForAction(action, extension); const subrows = this.subRowsForAction(action, extension);
} else { await this.updateAction(action, extension, { subrows });
action.subrows = undefined;
} }
return; return;
} }
action.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.props.item,
@@ -69,18 +87,13 @@ class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
return ''; return '';
} }
); );
if (action.error) { if (response.error) {
await this.updateAction(action, extension, { error: true });
return; return;
} }
action.running = false; await this.updateAction(action, extension, { running: false });
this.handleActionResponse(action, response); this.handleActionResponse(action, response);
await this.application.actionsManager!.loadExtensionInContextOfItem( await this.reloadExtension(extension);
extension,
this.props.item
);
this.setState({
extensions: this.state.extensions
});
} }
handleActionResponse(action: Action, result: ActionResponse) { handleActionResponse(action: Action, result: ActionResponse) {
@@ -95,7 +108,7 @@ class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
} }
} }
subRowsForAction(parentAction: Action, extension: SNActionsExtension) { private subRowsForAction(parentAction: Action, extension: SNActionsExtension): ActionSubRow[] | undefined {
if (!parentAction.subactions) { if (!parentAction.subactions) {
return undefined; return undefined;
} }
@@ -110,6 +123,63 @@ class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
}; };
}); });
} }
private async updateAction(
action: Action,
extension: SNActionsExtension,
params: UpdateActionParams
) {
const updatedExtension = await this.application.changeItem(extension.uuid, (mutator) => {
const extensionMutator = mutator as ActionsExtensionMutator;
extensionMutator.actions = extension!.actions.map((act) => {
if (act && params && act.verb === action.verb && act.url === action.url) {
return {
...action,
running: params?.running,
error: params?.error,
subrows: params?.subrows || act?.subrows,
};
}
return act;
});
}) as SNActionsExtension;
await this.updateExtension(updatedExtension);
}
private async updateExtension(
extension: SNActionsExtension,
params?: UpdateExtensionParams
) {
const updatedExtension = await this.application.changeItem(extension.uuid, (mutator) => {
const extensionMutator = mutator as ActionsExtensionMutator;
extensionMutator.hidden = params && params.hidden;
}) as SNActionsExtension;
const extensions = this.state.extensions.map((ext: SNActionsExtension) => {
if (extension.uuid === ext.uuid) {
return updatedExtension;
}
return ext;
});
await this.setState({
extensions: extensions
});
}
private async reloadExtension(extension: SNActionsExtension) {
const extensionInContext = await this.application.actionsManager!.loadExtensionInContextOfItem(
extension,
this.props.item
);
const extensions = this.state.extensions.map((ext: SNActionsExtension) => {
if (extension.uuid === ext.uuid) {
return extensionInContext;
}
return ext;
});
this.setState({
extensions: extensions
});
}
} }
export class ActionsMenu extends WebDirective { export class ActionsMenu extends WebDirective {

View File

@@ -7,19 +7,24 @@
target='blank' target='blank'
) )
menu-row(label="'Download Actions'") menu-row(label="'Download Actions'")
div(ng-repeat='extension in self.state.extensions track by extension.uuid') 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; self.loadingExtensions = false')
.sk-menu-panel-header( .sk-menu-panel-header(
ng-click='extension.hide = !extension.hide; $event.stopPropagation();' ng-click='self.updateExtension(extension, { hidden: !extension.hidden }); $event.stopPropagation();'
) )
.sk-menu-panel-column .sk-menu-panel-column
.sk-menu-panel-header-title {{extension.name}} .sk-menu-panel-header-title {{extension.name}}
.sk-spinner.small.loading(ng-if='self.loadingState[extension.uuid]') div(ng-if='extension.hidden') …
div(ng-if='extension.hide') …
menu-row( menu-row(
action='self.executeAction(action, extension);', action='self.executeAction(action, extension)',
label='action.label', label='action.label',
ng-if='!extension.hide', ng-if='!extension.hidden',
ng-repeat='action in extension.actionsWithContextForItem(self.props.item)', ng-repeat='action in extension.actionsWithContextForItem(self.props.item) track by $index',
disabled='action.running'
spinner-class="action.running ? 'info' : null", spinner-class="action.running ? 'info' : null",
sub-rows='action.subrows', sub-rows='action.subrows',
subtitle='action.desc' subtitle='action.desc'