Skip to content

Provide support for scopes #756

@tdwesten

Description

@tdwesten

Hallo,

I'm currently working on an app that utilizes a custom service for shortcuts. This service includes a scope that indicates the app's current state. It would greatly benefit me to have a centrally managed scope for easier migration. Is there a solution available that I might have missed?

My current ShortcutsService;

import RouterService from '@ember/routing/router-service';
import Service, { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import CommandPaletteService from './command-palette';
import EventBusService from './event-bus';
import { ShortCutScope, ShortCut } from '../types/app';

/**
 * ShortcutsService
 */
export default class ShortcutsService extends Service {
  @service declare router: RouterService;
  @service declare eventBus: EventBusService;
  // prettier-ignore
  @service('command-palette') declare commandPaletteService: CommandPaletteService;

  // Defaults
  @tracked scope = ShortCutScope.All;

  // prettier-ignore
  shortcuts: ShortCut[] = [
    {shortcut: 'command+1', scope: ShortCutScope.All, callback: () => this.router.transitionTo('documents') },
    {shortcut: 'command+2', scope: ShortCutScope.All, callback: () => this.router.transitionTo('import') },
    {shortcut: 'command+3', scope: ShortCutScope.All, callback: () => this.router.transitionTo('senders') },
    {shortcut: 'command+,', scope: ShortCutScope.All, callback: () => this.router.transitionTo('settings') },
    {shortcut: 'command+k', scope: ShortCutScope.All, callback: () => this.commandPaletteService.open() },
    {shortcut: 'escape', scope: ShortCutScope.CommandPalette, callback: () => this.commandPaletteService.close() },
    {shortcut: 'arrowup', scope: ShortCutScope.CommandPalette, callback: () => this.commandPaletteService.indexChange(false) },
    {shortcut: 'arrowdown', scope: ShortCutScope.CommandPalette, callback: () => this.commandPaletteService.indexChange(true) },
  ];

  // Id's of input elements that should not be filtered
  excludedInputIds = ['command-palette-input'];

  /**
   * Setup the shortcuts
   * @returns {void}
   */
  setup(): void {
    document.addEventListener('keydown', (event) => {
      this.handleKeyDown(event);
    });
  }

  /**
   * Set the scope
   * @param {ShortCutScope} scope
   */
  setScope(scope: ShortCutScope): void {
    this.scope = scope;
  }

  /**
   * Get the scope
   * @returns {ShortCutScope}
   */
  get getScope(): ShortCutScope {
    return this.scope;
  }

  /**
   * Handle key down event
   * @param {KeyboardEvent} event
   */
  handleKeyDown(event: KeyboardEvent): void {
    if (this.filterEvent(event)) {
      return;
    }

    const shortcut = this.findShortcut(event);

    if (shortcut) {
      event.preventDefault();
      event.stopPropagation();

      shortcut.callback();
    }
  }

  /**
   * Filter the event for input elements
   *
   * @param {KeyboardEvent} event
   */
  filterEvent(event: KeyboardEvent): boolean {
    const target = event.target as HTMLElement;

    if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
      if (this.excludedInputIds.includes(target.id)) {
        return false;
      }

      return true;
    }

    return false;
  }

  /**
   * Find a shortcut
   * @param {KeyboardEvent} event
   * @returns {ShortCut | undefined}
   */
  findShortcut(event: KeyboardEvent): ShortCut | undefined {
    const key = event.key.toLowerCase();
    const isCommand = event.metaKey || event.ctrlKey;
    const shortcut = `${isCommand ? 'command+' : ''}${key}`;

    return this.shortcuts.find((s) => {
      return (
        s.shortcut === shortcut &&
        (s.scope === this.scope || s.scope === ShortCutScope.All)
      );
    });
  }
}

// Don't remove this declaration: this is what enables TypeScript to resolve
// this service using `Owner.lookup('service:shortcuts')`, as well
// as to check when you pass the service name as an argument to the decorator,
// like `@service('shortcuts') declare altName: ShortcutsService;`.
declare module '@ember/service' {
  interface Registry {
    shortcuts: ShortcutsService;
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions