Anki/ts/lib/tslib/keys.ts
Damien Elmes 9f55cf26fc
Switch to SvelteKit (#3077)
* Update to latest Node LTS

* Add sveltekit

* Split tslib into separate @generated and @tslib components

SvelteKit's path aliases don't support multiple locations, so our old
approach of using @tslib to refer to both ts/lib and out/ts/lib will no
longer work. Instead, all generated sources and their includes are
placed in a separate out/ts/generated folder, and imported via @generated
instead. This also allows us to generate .ts files, instead of needing
to output separate .d.ts and .js files.

* Switch package.json to module type

* Avoid usage of baseUrl

Incompatible with SvelteKit

* Move sass into ts; use relative links

SvelteKit's default sass support doesn't allow overriding loadPaths

* jest->vitest, graphs example working with yarn dev

* most pages working in dev mode

* Some fixes after rebasing

* Fix/silence some svelte-check errors

* Get image-occlusion working with Fabric types

* Post-rebase lock changes

* Editor is now checked

* SvelteKit build integrated into ninja

* Use the new SvelteKit entrypoint for pages like congrats/deck options/etc

* Run eslint once for ts/**; fix some tests

* Fix a bunch of issues introduced when rebasing over latest main

* Run eslint fix

* Fix remaining eslint+pylint issues; tests now all pass

* Fix some issues with a clean build

* Latest bufbuild no longer requires @__PURE__ hack

* Add a few missed dependencies

* Add yarn.bat to fix Windows build

* Fix pages failing to show when ANKI_API_PORT not defined

* Fix svelte-check and vitest on Windows

* Set node path in ./yarn

* Move svelte-kit output to ts/.svelte-kit

Sadly, I couldn't figure out a way to store it in out/ if out/ is
a symlink, as it breaks module resolution when SvelteKit is run.

* Allow HMR inside Anki

* Skip SvelteKit build when HMR is defined

* Fix some post-rebase issues

I should have done a normal merge instead.
2024-03-31 09:16:31 +01:00

133 lines
3.9 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "@generated/ftl";
import { isApplePlatform } from "./platform";
// those are the modifiers that Anki works with
export type Modifier = "Control" | "Alt" | "Shift" | "Meta";
const allModifiers: Modifier[] = ["Control", "Alt", "Shift", "Meta"];
const platformModifiers: string[] = isApplePlatform()
? ["Meta", "Alt", "Shift", "Control"]
: ["Control", "Alt", "Shift", "OS"];
function translateModifierToPlatform(modifier: Modifier): string {
return platformModifiers[allModifiers.indexOf(modifier)];
}
export function checkIfModifierKey(event: KeyboardEvent): boolean {
// At least the web view on Desktop Anki gives out the wrong values for
// `event.location`, which is why we do it like this.
let isInputKey = false;
for (const modifier of allModifiers) {
isInputKey ||= event.code.startsWith(modifier);
}
return isInputKey;
}
export function keyboardEventIsPrintableKey(event: KeyboardEvent): boolean {
return event.key.length === 1;
}
export const checkModifiers = (required: Modifier[], optional: Modifier[] = []) => (event: KeyboardEvent): boolean => {
return allModifiers.reduce(
(
matches: boolean,
currentModifier: Modifier,
currentIndex: number,
): boolean =>
matches
&& (optional.includes(currentModifier as Modifier)
|| event.getModifierState(platformModifiers[currentIndex])
=== required.includes(currentModifier)),
true,
);
};
const modifierPressed = (modifier: Modifier) => (event: MouseEvent | KeyboardEvent): boolean => {
const translated = translateModifierToPlatform(modifier);
const state = event.getModifierState(translated);
return event.type === "keyup"
? state && (event as KeyboardEvent).key !== translated
: state;
};
export const controlPressed = modifierPressed("Control");
export const shiftPressed = modifierPressed("Shift");
export const altPressed = modifierPressed("Alt");
export const metaPressed = modifierPressed("Meta");
export function modifiersToPlatformString(modifiers: string[]): string {
const displayModifiers = isApplePlatform()
? ["^", "⌥", "⇧", "⌘"]
: [`${tr.keyboardCtrl()}+`, "Alt+", `${tr.keyboardShift()}+`, "Win+"];
let result = "";
for (const modifier of modifiers) {
result += displayModifiers[platformModifiers.indexOf(modifier)];
}
return result;
}
export function keyToPlatformString(key: string): string {
switch (key) {
case "Backspace":
return "⌫";
case "Delete":
return "⌦";
case "Escape":
return "⎋";
default:
return key;
}
}
export function isArrowLeft(event: KeyboardEvent): boolean {
if (event.code === "ArrowLeft") {
return true;
}
return isApplePlatform() && metaPressed(event) && event.code === "KeyB";
}
export function isArrowRight(event: KeyboardEvent): boolean {
if (event.code === "ArrowRight") {
return true;
}
return isApplePlatform() && metaPressed(event) && event.code === "KeyF";
}
export function isArrowUp(event: KeyboardEvent): boolean {
if (event.code === "ArrowUp") {
return true;
}
return isApplePlatform() && metaPressed(event) && event.code === "KeyP";
}
export function isArrowDown(event: KeyboardEvent): boolean {
if (event.code === "ArrowDown") {
return true;
}
return isApplePlatform() && metaPressed(event) && event.code === "KeyN";
}
export function onEnterOrSpace(callback: () => void): (event: KeyboardEvent) => void {
return (event: KeyboardEvent) => {
switch (event.code) {
case "Enter":
case "Space":
callback();
break;
}
};
}