Skip to content
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
2 changes: 1 addition & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ module.exports = function(config) {
autoWatchBatchDelay: 1000,

browsers: ["CustomElectron"],
browserNoActivityTimeout: 300000,
browserNoActivityTimeout: 3000000,
customLaunchers: {
CustomElectron: {
base: "Electron",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CommandRegistry } from "./command-registry";

const cmd1 = {
id: "foo",
description: "My foo command",
binding: "ctrl+f",
execute: () => null,
};
Expand All @@ -24,6 +25,7 @@ describe("CommandRegistry", () => {
const cmd2 = {
id: "bar",
binding: "ctrl+b",
description: "My bar command",
when: (context) => context.has("isFocused"),
execute: () => null,
};
Expand All @@ -38,6 +40,7 @@ describe("CommandRegistry", () => {

const cmd2 = {
id: "foo",
description: "My bar command",
binding: "ctrl+b",
when: (context) => context.has("isFocused"),
execute: () => null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CommandContext } from "../context";

export interface Command {
id: string;
description: string;
binding: string;
when?: (context: CommandContext) => boolean;
execute: (injector: Injector, context: CommandContext) => Promise<any> | void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@ describe("Keybinding service", () => {

CommandRegistry.register({
id: "foo",
description: "My foo command",
binding: "ctrl+f",
execute: cmd1Spy,
});
CommandRegistry.register({
id: "bar",
description: "My bar command",
binding: "ctrl+b",
when: (context) => context.has("barAllowed"),
execute: cmd2Spy,
});
CommandRegistry.register({
id: "barAlt",
description: "My other command",
binding: "ctrl+b",
when: (context) => !context.has("barAllowed"),
execute: cmd3Spy,
Expand Down
35 changes: 20 additions & 15 deletions src/@batch-flask/core/commands/keybindings/keybindings.service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Injectable, Injector } from "@angular/core";
import { KeyModifier } from "@batch-flask/core/keys";
import { log } from "@batch-flask/utils";
import { Subscription, fromEvent, merge } from "rxjs";
import { BehaviorSubject, Observable, Subscription, fromEvent, merge } from "rxjs";
import { map, tap } from "rxjs/operators";
import { Command, CommandRegistry } from "../command-registry";
import { CommandContext, ContextService } from "../context";

@Injectable({ providedIn: "root" })
export class KeyBindingsService {
private _keyBindings = new Map<string, Command[]>();
constructor(private contextService: ContextService, private injector: Injector) { }
public keyBindings: Observable<Map<string, Command[]>>;
private _keyBindings = new BehaviorSubject(new Map<string, Command[]>());
constructor(private contextService: ContextService, private injector: Injector) {
this.keyBindings = this._keyBindings.asObservable();
}

public listen(): Subscription {
this._loadCommands();
Expand All @@ -36,8 +39,8 @@ export class KeyBindingsService {
}

public dispatch(binding: KeyBinding, context: CommandContext): boolean {
if (this._keyBindings.has(binding.hash)) {
const commands = this._keyBindings.get(binding.hash)!;
if (this._keyBindings.value.has(binding.hash)) {
const commands = this._keyBindings.value.get(binding.hash)!;
const matchingCommands = commands.filter(x => x.when == null || x.when(context));
if (matchingCommands.length === 0) {
return false;
Expand All @@ -53,28 +56,30 @@ export class KeyBindingsService {
}

private _loadCommands() {
const map = new Map<string, Command[]>();
const commands = CommandRegistry.getCommands();
for (const command of commands) {
const binding = parseKeyBinding(command.binding);
if (this._keyBindings.has(binding.hash)) {
this._keyBindings.get(binding.hash)!.push(command);
const binding = KeyBinding.parse(command.binding);
if (map.has(binding.hash)) {
map.get(binding.hash)!.push(command);
} else {
this._keyBindings.set(binding.hash, [command]);
map.set(binding.hash, [command]);
}
}
}
}

export function parseKeyBinding(value: string): KeyBinding {
const keys = value.replace(/ /g, "").toLowerCase().split("+");
return new KeyBinding(keys);
this._keyBindings.next(map);
}
}

export class KeyBinding {
public static parse(value: string): KeyBinding {
const keys = value.replace(/ /g, "").toLowerCase().split("+");
return new KeyBinding(keys);
}

public readonly mods: KeyModifier[] = [];
public readonly keys: string[] = [];
public readonly hash: string;

constructor(keys: string[]) {
const result = this._extractModifiers(keys);
this.keys = result.keys;
Expand Down
1 change: 1 addition & 0 deletions src/@batch-flask/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from "./entity-commands";
export * from "./file";
export * from "./form";
export * from "./i18n";
export * from "./keybindings";
export * from "./loading";
export * from "./metrics-monitor";
export * from "./notifications";
Expand Down
2 changes: 2 additions & 0 deletions src/@batch-flask/ui/keybindings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./keybindings.module";
export * from "./keybindings.component";
166 changes: 166 additions & 0 deletions src/@batch-flask/ui/keybindings/keybindings.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { Component, DebugElement } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { By } from "@angular/platform-browser";
import { RouterTestingModule } from "@angular/router/testing";
import { CommandRegistry, KeyBindingsService } from "@batch-flask/core";
import { ElectronTestingModule } from "@batch-flask/electron/testing";
import { of } from "rxjs";
import { updateInput } from "test/utils/helpers";
import { FormModule } from "..";
import { TableTestingModule } from "../testing";
import { KeyBindingsComponent } from "./keybindings.component";

@Component({
template: `<bl-keybindings></bl-keybindings>`,
})
class TestComponent {
}

const fooCmd = {
id: "foo",
description: "My foo command",
binding: "ctrl+f",
execute: () => null,
};

const barCmd = {
id: "bar",
description: "My bar command",
binding: "ctrl+b",
when: (context) => context.has("barAllowed"),
execute: () => null,
};

const barAltCmd = {
id: "barAlt",
description: "My other command",
binding: "ctrl+b",
when: (context) => !context.has("barAllowed"),
execute: () => null,
};

const overrideCmd = {
id: "override",
description: "My command override",
binding: "ctrl+d",
execute: () => null,
};

const keybindingsMap = new Map()
.set("ctrl+f", [fooCmd])
.set("ctrl+b", [barCmd, barAltCmd])
.set("ctrl+o", [overrideCmd]);

describe("KeyBindingsComponent", () => {
let fixture: ComponentFixture<TestComponent>;
let de: DebugElement;
let keyBindingServiceSpy;
let searchEl: DebugElement;

beforeEach(() => {

keyBindingServiceSpy = {
keyBindings: of(keybindingsMap),
};
CommandRegistry.register(fooCmd);
CommandRegistry.register(barCmd);
CommandRegistry.register(barAltCmd);
CommandRegistry.register(overrideCmd);

TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
FormModule,
TableTestingModule,
ElectronTestingModule,
RouterTestingModule,
],
declarations: [KeyBindingsComponent, TestComponent],
providers: [
{ provide: KeyBindingsService, useValue: keyBindingServiceSpy },
],
});
fixture = TestBed.createComponent(TestComponent);
de = fixture.debugElement.query(By.css("bl-keybindings"));
fixture.detectChanges();

searchEl = de.query(By.css("input.search"));
});

afterEach(() => {
(CommandRegistry as any)._commands.clear();
});

function getRows() {
return de.queryAll(By.css("bl-row-render"));
}

function getCells(row: DebugElement) {
return row.queryAll(By.css(".bl-table-cell"));
}

it("shows all commands with their binding", () => {
const rows = getRows();
expect(rows.length).toBe(4);
const row0Cells = getCells(rows[0]);
expect(row0Cells[0].nativeElement.textContent).toContain("My foo command");
expect(row0Cells[1].nativeElement.textContent).toContain("ctrl+f");
expect(row0Cells[2].nativeElement.textContent).toContain("Default");
expect(row0Cells[2].nativeElement.textContent).not.toContain("User");

const row1Cells = getCells(rows[1]);
expect(row1Cells[0].nativeElement.textContent).toContain("My bar command");
expect(row1Cells[1].nativeElement.textContent).toContain("ctrl+b");
expect(row1Cells[2].nativeElement.textContent).toContain("Default");
expect(row1Cells[2].nativeElement.textContent).not.toContain("User");

const row2Cells = getCells(rows[2]);
expect(row2Cells[0].nativeElement.textContent).toContain("My other command");
expect(row2Cells[1].nativeElement.textContent).toContain("ctrl+b");
expect(row2Cells[2].nativeElement.textContent).toContain("Default");
expect(row2Cells[2].nativeElement.textContent).not.toContain("User");

const row3Cells = getCells(rows[3]);
expect(row3Cells[0].nativeElement.textContent).toContain("My command override");
expect(row3Cells[1].nativeElement.textContent).toContain("ctrl+o");
expect(row3Cells[2].nativeElement.textContent).toContain("User");
expect(row3Cells[2].nativeElement.textContent).not.toContain("Default");
});

it("filter the rows by description", () => {
updateInput(searchEl, "foo");
fixture.detectChanges();

let rows = getRows();
expect(rows.length).toBe(1);

expect(getCells(rows[0])[0].nativeElement.textContent).toContain("My foo command");

updateInput(searchEl, "bar");
fixture.detectChanges();

rows = getRows();
expect(rows.length).toBe(1);
expect(getCells(rows[0])[0].nativeElement.textContent).toContain("My bar command");
});

it("filter the rows by shortcut", () => {
updateInput(searchEl, `"ctrl+o"`);
fixture.detectChanges();

let rows = getRows();
expect(rows.length).toBe(1);

expect(getCells(rows[0])[0].nativeElement.textContent).toContain("My command override");

updateInput(searchEl, `"ctrl+b`);
fixture.detectChanges();

rows = getRows();
expect(rows.length).toBe(2);
expect(getCells(rows[0])[0].nativeElement.textContent).toContain("My bar command");
expect(getCells(rows[1])[0].nativeElement.textContent).toContain("My other command");
});
});
Loading