mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00

* 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.
133 lines
3.9 KiB
TypeScript
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;
|
|
}
|
|
};
|
|
}
|