Skip to content

Commit

Permalink
improve(frontend): hotkeyの改修
Browse files Browse the repository at this point in the history
  • Loading branch information
taiyme committed Jun 13, 2024
1 parent a1bd8a1 commit 1a0cc4d
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 123 deletions.
2 changes: 1 addition & 1 deletion packages/frontend/src/boot/main-boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import { useStream } from '@/stream.js';
import * as sound from '@/scripts/sound.js';
import { $i, signout, updateAccount } from '@/account.js';
import { ColdDeviceStorage, defaultStore } from '@/store.js';
import { makeHotkey } from '@/scripts/hotkey.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
import { initializeSw } from '@/scripts/initialize-sw.js';
import { deckStore } from '@/ui/deck/deck-store.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mainRouter } from '@/router/main.js';
import { makeHotkey } from '@/scripts/tms/hotkey.js';

export async function mainBoot() {
const { isClientUpdated } = await common(() => createApp(
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/directives/hotkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { Directive } from 'vue';
import { makeHotkey } from '../scripts/hotkey.js';
import { makeHotkey } from '@/scripts/tms/hotkey.js';

export default {
mounted(el, binding) {
Expand Down
96 changes: 0 additions & 96 deletions packages/frontend/src/scripts/hotkey.ts

This file was deleted.

25 changes: 0 additions & 25 deletions packages/frontend/src/scripts/keycode.ts

This file was deleted.

106 changes: 106 additions & 0 deletions packages/frontend/src/scripts/tms/hotkey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { getHTMLElementOrNull } from '@/scripts/tms/get-or-null.js';

//#region types
type Callback = (ev: KeyboardEvent) => unknown;

type Keymap = Record<string, Callback>;

type Pattern = {
which: string[];
ctrl: boolean;
alt: boolean;
shift: boolean;
};

type Action = {
patterns: Pattern[];
callback: Callback;
};
//#endregion

//#region consts
const KEY_ALIASES = {
'esc': 'Escape',
'enter': 'Enter',
'space': ' ',
'up': 'ArrowUp',
'down': 'ArrowDown',
'left': 'ArrowLeft',
'right': 'ArrowRight',
'plus': ['+', ';'],
};

const IGNORE_ELEMENTS = ['input', 'textarea'];

const ALLOW_REPEAT_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
//#endregion

//#region impl
export const makeHotkey = (keymap: Keymap) => {
const actions = parseKeymap(keymap);
return (ev: KeyboardEvent) => {
if (ev.repeat && !ALLOW_REPEAT_KEYS.includes(ev.key)) return;
if ('pswp' in window && window.pswp != null) return;
if (document.activeElement != null) {
if (IGNORE_ELEMENTS.includes(document.activeElement.tagName.toLowerCase())) return;
if (getHTMLElementOrNull(document.activeElement)?.isContentEditable) return;
}
for (const { patterns, callback } of actions) {
if (matchPatterns(ev, patterns)) {
ev.preventDefault();
ev.stopPropagation();
callback(ev);
}
}
};
};

const parseKeymap = (keymap: Keymap) => {
return Object.entries(keymap).map(([rawPatterns, callback]) => {
const patterns = parsePatterns(rawPatterns);
return { patterns, callback } as const satisfies Action;
});
};

const parsePatterns = (rawPatterns: keyof Keymap) => {
return rawPatterns.split('|').map(part => {
const keys = part.split('+').map(x => x.trim().toLowerCase());
const which = parseKeyCode(keys.findLast(x => !['ctrl', 'alt', 'shift'].includes(x)));
const ctrl = keys.includes('ctrl');
const alt = keys.includes('alt');
const shift = keys.includes('shift');
return { which, ctrl, alt, shift } as const satisfies Pattern;
});
};

const matchPatterns = (ev: KeyboardEvent, patterns: Action['patterns']) => {
const key = ev.key.toLowerCase();
return patterns.some(({ which, ctrl, shift, alt }) => {
if (!which.includes(key)) return false;
if (ctrl !== (ev.ctrlKey || ev.metaKey)) return false;
if (alt !== ev.altKey) return false;
if (shift !== ev.shiftKey) return false;
return true;
});
};

const parseKeyCode = (input?: string | null) => {
if (input == null) return [];
const raw = getValueByKey(KEY_ALIASES, input);
if (raw == null) return [input];
if (typeof raw === 'string') return [raw];
return [...raw];
};

const getValueByKey = <
T extends Record<keyof any, unknown>,
K extends keyof T | keyof any,
>(obj: T, key: K) => {
return obj[key] as K extends keyof T ? T[K] : T[keyof T] | undefined;
};
//#endregion

0 comments on commit 1a0cc4d

Please sign in to comment.