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

Provide support for scopes #756

Open
tdwesten opened this issue Sep 13, 2023 · 0 comments
Open

Provide support for scopes #756

tdwesten opened this issue Sep 13, 2023 · 0 comments

Comments

@tdwesten
Copy link

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;
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant