Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(action): add a way to reactively set an actionbar item's availab… #425

Merged
merged 3 commits into from
Sep 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions demo/src/app/common/action/action.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Component, OnInit, OnDestroy } from '@angular/core';

import { BehaviorSubject } from 'rxjs';

import {
Media,
MediaOrientation,
Expand All @@ -17,7 +19,7 @@ import { Overlay } from '@angular/cdk/overlay';
export class AppActionComponent implements OnInit, OnDestroy {
public store = new ActionStore([]);

private added = false;
private added$ = new BehaviorSubject(false);

get actionbarMode(): ActionbarMode {
const media = this.mediaService.media$.value;
Expand All @@ -35,8 +37,6 @@ export class AppActionComponent implements OnInit, OnDestroy {
) {}

ngOnInit() {
const added = () => this.added === true;

this.store.load([
{
id: 'add',
Expand All @@ -45,20 +45,19 @@ export class AppActionComponent implements OnInit, OnDestroy {
tooltip: 'Add Tooltip',
handler: () => {
alert('Add!');
this.added = true;
this.store.updateActionsAvailability();
this.added$.next(true);
}
},
{
id: 'edit',
title: 'Edit',
icon: 'pencil',
tooltip: 'Edit Tooltip',
args: ['1'],
handler: (item: string) => {
alert(`Edit item ${item}!`);
},
conditions: [added],
args: ['1']
availability: () => this.added$
},
{
id: 'delete',
Expand All @@ -67,13 +66,11 @@ export class AppActionComponent implements OnInit, OnDestroy {
icon: 'delete',
handler: () => {
alert('Delete!');
this.added = false;
this.store.updateActionsAvailability();
this.added$.next(false);
},
conditions: [added]
availability: () => this.added$
}
]);
this.store.updateActionsAvailability();
}

ngOnDestroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
matTooltipClass="actionbarItemTooltip"
matTooltipShowDelay="500"
[matTooltip]="tooltip | translate"
[ngClass]="{'igo-actionbar-item-disabled': disabled}"
[ngClass]="ngClass$ | async"
(click)="onClick()">
<button *ngIf="withIcon"
mat-list-avatar
mat-icon-button
[color]="color"
[disabled]="disabled">
[disabled]="disabled$ | async">
<mat-icon *ngIf="withIcon" svgIcon="{{icon}}"></mat-icon>
</button>
<h4 *ngIf="withTitle" matLine>{{title | translate}}</h4>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import {
Input,
Output,
EventEmitter,
ChangeDetectionStrategy
ChangeDetectionStrategy,
OnInit,
OnDestroy
} from '@angular/core';

import { BehaviorSubject, Subscription } from 'rxjs';

import { Action } from '../shared/action.interfaces';

/**
Expand All @@ -17,7 +21,17 @@ import { Action } from '../shared/action.interfaces';
styleUrls: ['./actionbar-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActionbarItemComponent {
export class ActionbarItemComponent implements OnInit, OnDestroy {

readonly disabled$: BehaviorSubject<boolean> = new BehaviorSubject(false);

readonly ngClass$: BehaviorSubject<{[key: string]: boolean}> = new BehaviorSubject({});

private ngClass$$: Subscription;

private disabled$$: Subscription;

private availability$$: Subscription;

/**
* Action
Expand All @@ -42,7 +56,9 @@ export class ActionbarItemComponent {
/**
* Whether the action is disabled
*/
@Input() disabled = false;
@Input()
set disabled(value: boolean) { this.disabled$.next(value); }
get disabled(): boolean { return this.disabled$.value; }

/**
* Event emitted when the action button is clicked
Expand All @@ -66,6 +82,37 @@ export class ActionbarItemComponent {

constructor() {}

ngOnInit() {
const args = this.action.args || [];

if (this.action.ngClass !== undefined) {
this.ngClass$$ = this.action.ngClass(...args)
.subscribe((ngClass: {[key: string]: boolean}) => this.updateNgClass(ngClass));
}

if (this.action.availability !== undefined) {
this.availability$$ = this.action.availability(...args)
.subscribe((available: boolean) => this.disabled = !available);
}

this.disabled$$ = this.disabled$
.subscribe((disabled: boolean) => this.updateNgClass({'igo-actionbar-item-disabled': disabled}));
}

ngOnDestroy() {
if (this.ngClass$$ !== undefined) {
this.ngClass$$.unsubscribe();
this.ngClass$$ = undefined;
}

if (this.availability$$ !== undefined) {
this.availability$$.unsubscribe();
this.availability$$ = undefined;
}

this.disabled$$.unsubscribe();
}

/**
* When the action button is clicked, emit the 'trigger' event but don't
* invoke the action handler. This is handled by the parent component.
Expand All @@ -77,4 +124,8 @@ export class ActionbarItemComponent {
}
this.trigger.emit(this.action);
}

private updateNgClass(ngClass: {[key: string]: boolean}) {
this.ngClass$.next(Object.assign({}, this.ngClass$.value, ngClass));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

<ng-template #buttonContent *ngIf="!collapsed" ngFor let-action [ngForOf]="store.view.all$() | async">
<igo-actionbar-item
[ngClass]="itemClassFunc(action)"
color="accent"
[withTitle]="withTitle"
[withIcon]="withIcon"
Expand Down Expand Up @@ -73,7 +72,6 @@
[withTitle]="withTitle"
[withIcon]="withIcon"
[color]="color"
[disabled]="store.state.get(action).disabled"
[action]="action"
(trigger)="onTriggerAction(action)">
</igo-actionbar-item>
Expand All @@ -89,7 +87,6 @@
[withTitle]="withTitle"
[withIcon]="withIcon"
[color]="color"
[disabled]="store.state.get(action).disabled"
[action]="action"
(trigger)="onTriggerAction(action)">
</igo-actionbar-item>
Expand Down
10 changes: 0 additions & 10 deletions packages/common/src/lib/action/actionbar/actionbar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,6 @@ export class ActionbarComponent implements OnDestroy, OnChanges {
}
private _overlayClass = '';

/**
* Function to add class to item actionbar
*/
@Input() itemClassFunc: (action: Action) => { [key: string]: boolean } =
ActionbarComponent.defaultItemClassFunc;

/**
* @ignore
*/
Expand Down Expand Up @@ -196,10 +190,6 @@ export class ActionbarComponent implements OnDestroy, OnChanges {
return this.mediaService.getMedia() === Media.Desktop;
}

static defaultItemClassFunc(action: Action) {
return {};
}

constructor(
public overlay: Overlay,
private elRef: ElementRef,
Expand Down
6 changes: 4 additions & 2 deletions packages/common/src/lib/action/shared/action.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Observable } from 'rxjs';

export interface Action {
id: string;
handler: ActionHandler;
title?: string;
icon?: string;
tooltip?: string;
conditions?: Array<(...args: any[]) => boolean>;
args?: any[];
conditionArgs?: any[];
availability?: (...args: any[]) => Observable<boolean>;
ngClass?: (...args: any[]) => Observable<{[key: string]: boolean}>;
}

export type ActionHandler = (...args: any[]) => void;
31 changes: 0 additions & 31 deletions packages/common/src/lib/action/shared/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,4 @@ import { Action } from './action.interfaces';
*/
export class ActionStore extends EntityStore<Action> {

/**
* Update actions availability. That means disabling or enabling some
* actions based on the conditions they define.
*/
updateActionsAvailability() {
const availables = [];
const unavailables = [];

this.entities$.value.forEach((action: Action) => {
const conditions = action.conditions || [];
const args = action.conditionArgs || [];
const available = conditions.every((condition: (...args: any[]) => boolean) => {
return condition(...args);
});
available ? availables.push(action) : unavailables.push(action);
});

if (unavailables.length > 0) {
this.state.updateMany(unavailables, {
disabled: true,
active: false
});
}

if (availables.length > 0) {
this.state.updateMany(availables, {
disabled: false
});
}
}

}
3 changes: 1 addition & 2 deletions packages/common/src/lib/tool/toolbox/toolbox.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
[withIcon]="true"
[withTitle]="toolbarWithTitle"
[scrollActive]="toolbarWithTitle"
[horizontal]="false"
[itemClassFunc]="actionBarItemClassFunc">
[horizontal]="false">
</igo-actionbar>

<div
Expand Down
38 changes: 21 additions & 17 deletions packages/common/src/lib/tool/toolbox/toolbox.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@angular/core';

import { Subscription, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';

import { Action, ActionStore } from '../../action';
import { Tool } from '../shared/tool.interface';
Expand Down Expand Up @@ -130,23 +131,6 @@ export class ToolboxComponent implements OnInit, OnDestroy {
return tool.options || {};
}

/**
* Get Action bar item class function
* @internal
*/
get actionBarItemClassFunc() {
return (tool: Tool) => {
if (!this.toolbox.activeTool$.value) {
return;
} else {
if (this.toolbox.activeTool$.value.parent) {
return { 'children-tool-actived': tool.id === this.toolbox.activeTool$.value.parent };
}
return { 'tool-actived': tool.id === this.toolbox.activeTool$.value.name };
}
};
}

/**
* Initialize an action store
* @param toolbar Toolbar
Expand Down Expand Up @@ -206,6 +190,26 @@ export class ToolboxComponent implements OnInit, OnDestroy {
args: [tool, this.toolbox],
handler: (_tool: Tool, _toolbox: Toolbox) => {
_toolbox.activateTool(_tool.name);
},
ngClass: (_tool: Tool, _toolbox: Toolbox) => {
return this.toolbox.activeTool$.pipe(
map((activeTool: Tool) => {
let toolActivated = false;
if (activeTool !== undefined && _tool.name === activeTool.name) {
toolActivated = true;
}

let childrenToolActivated = false;
if (activeTool !== undefined && _tool.name === activeTool.parent) {
childrenToolActivated = true;
}

return {
'tool-activated': toolActivated,
'children-tool-activated': childrenToolActivated
};
})
);
}
});
return acc;
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/lib/tool/toolbox/toolbox.theming.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
$accent: map-get($theme, accent);


igo-toolbox > igo-actionbar igo-actionbar-item.tool-actived > mat-list-item {
igo-toolbox > igo-actionbar igo-actionbar-item mat-list-item.tool-activated {
background-color: mat-color($accent);
}

igo-toolbox > igo-actionbar igo-actionbar-item.children-tool-actived > mat-list-item {
igo-toolbox > igo-actionbar igo-actionbar-item mat-list-item.children-tool-activated {
background-color: mat-color($accent);
}

Expand Down
12 changes: 0 additions & 12 deletions packages/common/src/lib/workspace/shared/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,6 @@ export class Workspace<E extends object = object> {
.subscribe(() => this.onStateChange());
}

if (this.actionStore !== undefined) {
this.change$ = this.change
.pipe(debounceTime(35))
.subscribe(() => this.updateActionsAvailability());
}

this.change.next();
}

Expand Down Expand Up @@ -157,12 +151,6 @@ export class Workspace<E extends object = object> {
this.change.next();
}

updateActionsAvailability() {
if (this.actionStore !== undefined) {
this.actionStore.updateActionsAvailability();
}
}

/**
* When the state changes, update the actions availability.
*/
Expand Down
Loading