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:
@@ -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 {
|
||||||
|
|||||||
@@ -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'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user