initial commit
This commit is contained in:
commit
40cae008d8
191
.eslintrc.json
Normal file
191
.eslintrc.json
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"airbnb",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier",
|
||||||
|
"prettier/prettier",
|
||||||
|
"plugin:import/typescript"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"experimentalObjectRestSpread": true,
|
||||||
|
"jsx": true
|
||||||
|
},
|
||||||
|
"ecmaVersion": 9,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"react",
|
||||||
|
"@typescript-eslint",
|
||||||
|
"react-hooks"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"linebreak-style": [
|
||||||
|
"error",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"semi": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"react/jsx-uses-react": [
|
||||||
|
"error"
|
||||||
|
],
|
||||||
|
"react/jsx-uses-vars": [
|
||||||
|
"error"
|
||||||
|
],
|
||||||
|
"react-hooks/rules-of-hooks": "error",
|
||||||
|
// Checks rules of Hooks
|
||||||
|
"react-hooks/exhaustive-deps": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"additionalHooks": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Checks effect dependencies
|
||||||
|
"react/jsx-filename-extension": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"extensions": [
|
||||||
|
".tsx"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"import/extensions": [
|
||||||
|
"error",
|
||||||
|
"ignorePackages",
|
||||||
|
{
|
||||||
|
"js": "never",
|
||||||
|
"jsx": "never",
|
||||||
|
"ts": "never",
|
||||||
|
"tsx": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-shadow": "off",
|
||||||
|
"@typescript-eslint/no-shadow": [
|
||||||
|
"error"
|
||||||
|
],
|
||||||
|
"lines-between-class-members": [
|
||||||
|
"warn",
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"exceptAfterSingleLine": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"react/sort-comp": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"order": [
|
||||||
|
"static-variables",
|
||||||
|
"instance-variables",
|
||||||
|
"static-methods",
|
||||||
|
"lifecycle",
|
||||||
|
"render",
|
||||||
|
"/^render.+$/",
|
||||||
|
"instance-methods",
|
||||||
|
"everything-else"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-return-assign": [
|
||||||
|
"error",
|
||||||
|
"except-parens"
|
||||||
|
],
|
||||||
|
"import/no-extraneous-dependencies": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"devDependencies": [
|
||||||
|
"**/*",
|
||||||
|
"src/pages/!(api)/**/*.tsx",
|
||||||
|
"src/pages/**/*.tsx",
|
||||||
|
"src/!(pages|models|app)/**/*.(tsx|ts)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"react/destructuring-assignment": [
|
||||||
|
"error",
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"ignoreClassFields": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"react/state-in-constructor": [
|
||||||
|
"error",
|
||||||
|
"never"
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"vars": "all",
|
||||||
|
"ignoreRestSiblings": false,
|
||||||
|
"argsIgnorePattern": "^_",
|
||||||
|
"varsIgnorePattern": "^_"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"import/no-cycle": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"maxDepth": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-promise-executor-return": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-use-before-define": "off",
|
||||||
|
"@typescript-eslint/no-use-before-define": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"import/order": "off",
|
||||||
|
"import/prefer-default-export": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
|
"react/jsx-props-no-spreading": "off",
|
||||||
|
"react/jsx-boolean-value": "off",
|
||||||
|
"no-plusplus": "off",
|
||||||
|
"no-param-reassign": "off",
|
||||||
|
"default-case": "off",
|
||||||
|
"jsx-a11y/interactive-supports-focus": "off",
|
||||||
|
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||||
|
"jsx-a11y/click-events-have-key-events": "off",
|
||||||
|
"jsx-a11y/no-static-element-interactions": "off",
|
||||||
|
"jsx-a11y/label-has-associated-control": "off",
|
||||||
|
"react/require-default-props": "off"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
},
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": {
|
||||||
|
"extensions": [
|
||||||
|
".js",
|
||||||
|
".jsx",
|
||||||
|
".ts",
|
||||||
|
".tsx"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node": {
|
||||||
|
"extensions": [
|
||||||
|
".js",
|
||||||
|
".jsx",
|
||||||
|
".ts",
|
||||||
|
".tsx"
|
||||||
|
],
|
||||||
|
"moduleDirectory": [
|
||||||
|
"node_modules",
|
||||||
|
"src/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
}
|
||||||
|
}
|
||||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
/node_modules/*
|
||||||
|
node_modules/*
|
||||||
|
|
||||||
|
#idea-ide
|
||||||
|
/.idea/*
|
||||||
|
|
||||||
|
/src/hotkeys.ts
|
||||||
|
|
||||||
|
dist
|
||||||
2
.npnignore
Normal file
2
.npnignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/bin/
|
||||||
|
boostrapReactMobile.ts
|
||||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
55
bin/build.js
Normal file
55
bin/build.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const tmpFile = "./tmp/script.js";
|
||||||
|
|
||||||
|
function findNames(dir, excluded) {
|
||||||
|
let names = {};
|
||||||
|
if (excluded.includes(dir)) {
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
let files = fs.readdirSync(dir);
|
||||||
|
files.forEach(file => {
|
||||||
|
let stats = fs.statSync(dir + file);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
let nameObject = findNames(dir + file + '/', excluded);
|
||||||
|
names = Object.assign(names, nameObject);
|
||||||
|
} else if ((file.endsWith(".ts") ) && !excluded.includes(dir + file)) {
|
||||||
|
names[file.substring(0, file.length - 3)] = dir + file.substring(0, file.length - 3);
|
||||||
|
}
|
||||||
|
else if ((file.endsWith(".mjs") ) && !excluded.includes(dir + file)) {
|
||||||
|
names[file.substring(0, file.length - 4)] = dir + file.substring(0, file.length - 4);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildEntryPoints(fileOption, target) {
|
||||||
|
const cutLengthFront = 0;
|
||||||
|
|
||||||
|
target = target || tmpFile;
|
||||||
|
|
||||||
|
const resultDir = path.resolve(process.cwd(), path.dirname(target));
|
||||||
|
|
||||||
|
let names = {};
|
||||||
|
fileOption.input.forEach(dir => {
|
||||||
|
Object.assign(names, findNames(dir + "/", []));
|
||||||
|
});
|
||||||
|
|
||||||
|
let imports = '';
|
||||||
|
for (let k in names) {
|
||||||
|
imports += "export * from './" + path.relative(resultDir, path.resolve(process.cwd(), names[k].substring(cutLengthFront))) + "';\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(resultDir)) {
|
||||||
|
fs.mkdirSync(resultDir);
|
||||||
|
}
|
||||||
|
fs.writeFileSync(target, imports);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildEntryPoints({
|
||||||
|
input: [
|
||||||
|
path.resolve(process.cwd(), "src/"),
|
||||||
|
],
|
||||||
|
}, "./src/hotkeys.ts");
|
||||||
39
bin/release.sh
Executable file
39
bin/release.sh
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
# Exit when a command fails
|
||||||
|
set -e
|
||||||
|
|
||||||
|
REPOSITORY=git@github.com:Ainias/js-helper.git
|
||||||
|
|
||||||
|
if [[ -z "$1" ]]; then
|
||||||
|
echo "versioname not given!"
|
||||||
|
exit;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
versionName=$1
|
||||||
|
versionExists="$(git ls-remote $REPOSITORY refs/tags/"$versionName"| tr -d '\n')"
|
||||||
|
|
||||||
|
if [ -n "$versionExists" ]; then
|
||||||
|
echo "Version existiert bereits!";
|
||||||
|
exit 1;
|
||||||
|
fi;
|
||||||
|
WORKING_DIR=$(pwd)
|
||||||
|
TMPDIR=$(mktemp -d)
|
||||||
|
|
||||||
|
cd "$TMPDIR";
|
||||||
|
git clone $REPOSITORY project
|
||||||
|
cd project
|
||||||
|
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
git add -u
|
||||||
|
git commit -m "pre-version-commit for version $versionName" || echo "no commit needed"
|
||||||
|
npm version "$versionName"
|
||||||
|
npm publish
|
||||||
|
git push
|
||||||
|
|
||||||
|
cd "$WORKING_DIR"
|
||||||
|
git pull;
|
||||||
|
|
||||||
|
echo "$TMPDIR"
|
||||||
|
|
||||||
101
bin/updateCopies.js
Normal file
101
bin/updateCopies.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const packageName = require("../package.json").name;
|
||||||
|
|
||||||
|
let pathsToProjects = [
|
||||||
|
"/Users/sguenter/Projekte/Privat/dnd",
|
||||||
|
// "/home/silas/Projekte/web/nextjsTest/poc-nextjs",
|
||||||
|
// "/home/silas/Projekte/web/project-echo",
|
||||||
|
// "/home/silas/Projekte/web/smd-mail",
|
||||||
|
// "/home/silas/Projekte/web/dnd",
|
||||||
|
// "/home/silas/Projekte/web/bat",
|
||||||
|
// "/home/silas/Projekte/web/typeorm-sync",
|
||||||
|
// "/home/silas/Projekte/web/typeorm-sync-nextjs",
|
||||||
|
// "/home/silas/Projekte/web/worktime",
|
||||||
|
// "/home/silas/Projekte/web/TaskList",
|
||||||
|
// "/home/silas/Projekte/web/hoffnungsfest",
|
||||||
|
// "/home/silas/Projekte/web/geometry",
|
||||||
|
// "/home/silas/Projekte/web/react-bootstrap-mobile",
|
||||||
|
// "/home/silas/Projekte/web/react-bootstrap-mobile",
|
||||||
|
// "/home/silas/Projekte/chrome/dmscreen",
|
||||||
|
// "/home/silas/Projekte/web/smd-mail",
|
||||||
|
// "/home/silas/Projekte/web/prayercircle",
|
||||||
|
// "/home/silas/Projekte/Web/stories",
|
||||||
|
// "/home/silas/Projekte/web/cordova-sites",
|
||||||
|
// "/home/silas/Projekte/web/cordova-sites-easy-sync",
|
||||||
|
// "/home/silas/Projekte/Web/cordova-sites-user-management",
|
||||||
|
// "/home/silas/Projekte/i9/mbb",
|
||||||
|
// "/home/silas/Projekte/Web/bible-lexicon",
|
||||||
|
|
||||||
|
// "/var/www/i9/mbb",
|
||||||
|
// "/home/silas/PhpstormProjects/cordova-sites-user-management",
|
||||||
|
// "/home/silas/PhpstormProjects/project-echo",
|
||||||
|
];
|
||||||
|
|
||||||
|
const deleteFolderRecursive = function(path) {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
fs.readdirSync(path).forEach(function(file, index){
|
||||||
|
let curPath = path + "/" + file;
|
||||||
|
if (fs.lstatSync(curPath).isDirectory()) { // recurse
|
||||||
|
deleteFolderRecursive(curPath);
|
||||||
|
} else { // delete file
|
||||||
|
fs.unlinkSync(curPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fs.rmdirSync(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function execPromise(command) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log("executing " + command + "...");
|
||||||
|
exec(command, (err, stdout, stderr) => {
|
||||||
|
console.log(stdout);
|
||||||
|
console.log(stderr);
|
||||||
|
if (err) {
|
||||||
|
reject([err, stdout, stderr]);
|
||||||
|
} else {
|
||||||
|
resolve([stdout, stderr]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
execPromise("npm pack").then(async (std) => {
|
||||||
|
let thisPath = process.cwd();
|
||||||
|
let name = std[0].trim();
|
||||||
|
let pathToTar = path.resolve(thisPath, name);
|
||||||
|
|
||||||
|
if (!fs.existsSync("tmp")) {
|
||||||
|
fs.mkdirSync("tmp");
|
||||||
|
}
|
||||||
|
process.chdir("tmp");
|
||||||
|
await execPromise("tar -xvzf " + pathToTar + " -C ./");
|
||||||
|
process.chdir("package");
|
||||||
|
fs.unlinkSync("package.json");
|
||||||
|
|
||||||
|
let promise = Promise.resolve();
|
||||||
|
pathsToProjects.forEach((project) => {
|
||||||
|
promise = promise.then(async () => {
|
||||||
|
let resultDir = path.resolve(project, "node_modules", packageName);
|
||||||
|
console.log(resultDir, fs.existsSync(resultDir));
|
||||||
|
if (!fs.existsSync(resultDir)) {
|
||||||
|
fs.mkdirSync(resultDir);
|
||||||
|
}
|
||||||
|
return execPromise("cp -r ./* "+resultDir);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
process.chdir(thisPath);
|
||||||
|
fs.unlinkSync(name);
|
||||||
|
deleteFolderRecursive("tmp");
|
||||||
|
// fs.unlinkSync("tmp");
|
||||||
|
|
||||||
|
console.log("done!");
|
||||||
|
}).catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "@ainias42/hotkeys",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Hotkey manager",
|
||||||
|
"main": "dist/hotkeys",
|
||||||
|
"scripts": {
|
||||||
|
"build": "node bin/build.js & tsc",
|
||||||
|
"update packages": "npm run build && node bin/updateCopies.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Silas Günther",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.7.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||||
|
"@typescript-eslint/parser": "^6.7.3",
|
||||||
|
"eslint": "^8.50.0",
|
||||||
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
|
"eslint-plugin-import": "^2.28.1",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"@ainias42/js-helper": "^0.8.9"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/AffectedEnum.ts
Normal file
5
src/AffectedEnum.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum AffectedEnum {
|
||||||
|
NOT_FULL_FILLED = 0,
|
||||||
|
FULL_FILLED = 1,
|
||||||
|
FULL_FILLED_AND_CHANGED,
|
||||||
|
}
|
||||||
8
src/HotkeyDefinitionType.ts
Normal file
8
src/HotkeyDefinitionType.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { URecord } from '@ainias42/js-helper';
|
||||||
|
|
||||||
|
export const specialKeys = ['meta', 'control', 'alt', 'shift', 'tab'] as const;
|
||||||
|
|
||||||
|
export type HotkeyDefinitionType = {
|
||||||
|
keys: string[];
|
||||||
|
ignoreFormElements?: boolean;
|
||||||
|
} & URecord<(typeof specialKeys)[number], boolean>;
|
||||||
9
src/HotkeyListener.ts
Normal file
9
src/HotkeyListener.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { AffectedEnum } from './AffectedEnum';
|
||||||
|
|
||||||
|
export type HotkeyListenerEvent<SubKeys extends string | symbol | number> = {
|
||||||
|
event: KeyboardEvent;
|
||||||
|
type: 'keydown' | 'keyup';
|
||||||
|
subKeys: Record<SubKeys, AffectedEnum>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HotkeyListener<SubKeys extends string | symbol | number> = (ev: HotkeyListenerEvent<SubKeys>) => unknown;
|
||||||
171
src/HotkeyManager.ts
Normal file
171
src/HotkeyManager.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { HotkeyDefinitionType, specialKeys } from './HotkeyDefinitionType';
|
||||||
|
import { HotkeyListener } from './HotkeyListener';
|
||||||
|
import { JsonHelper, ObjectHelper } from '@ainias42/js-helper';
|
||||||
|
import { AffectedEnum } from './AffectedEnum';
|
||||||
|
|
||||||
|
type HotkeyEntry<SubKeys extends string> = {
|
||||||
|
keys: HotkeyDefinitionType[];
|
||||||
|
subKeys: { [key in SubKeys]: HotkeyDefinitionType[] };
|
||||||
|
callbacks: HotkeyListener<SubKeys>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export class HotkeyManager<HotkeyConfig extends Record<string, HotkeyEntry<string>>> {
|
||||||
|
private keyPressedMap = new Map<string, boolean>();
|
||||||
|
private hotkeys: HotkeyConfig;
|
||||||
|
private enabled = true;
|
||||||
|
|
||||||
|
constructor(hotkeys: HotkeyConfig) {
|
||||||
|
this.hotkeys = hotkeys;
|
||||||
|
this.addKeyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static isFormElement(element: EventTarget | null) {
|
||||||
|
return (
|
||||||
|
element instanceof HTMLInputElement ||
|
||||||
|
element instanceof HTMLSelectElement ||
|
||||||
|
element instanceof HTMLTextAreaElement
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static isEqual(a: HotkeyDefinitionType, b: HotkeyDefinitionType) {
|
||||||
|
return JsonHelper.deepEqual(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
addListener<HotkeyName extends keyof HotkeyConfig>(
|
||||||
|
hotkeyName: HotkeyName,
|
||||||
|
callback: HotkeyListener<keyof HotkeyConfig[HotkeyName]['subKeys']>,
|
||||||
|
) {
|
||||||
|
const { callbacks } = this.hotkeys[hotkeyName];
|
||||||
|
callbacks.push(callback);
|
||||||
|
return () => {
|
||||||
|
const index = callbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
callbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addHotKeyDefinition(hotkey: keyof HotkeyConfig, definition: HotkeyDefinitionType) {
|
||||||
|
this.hotkeys[hotkey].keys.push(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeHotkeyDefinition(hotkey: keyof HotkeyConfig, definition: HotkeyDefinitionType) {
|
||||||
|
this.hotkeys[hotkey].keys = this.hotkeys[hotkey].keys.filter((key) => !HotkeyManager.isEqual(key, definition));
|
||||||
|
}
|
||||||
|
|
||||||
|
setHotkeyDefinitions(hotkey: keyof HotkeyConfig, definitions: HotkeyDefinitionType[]) {
|
||||||
|
this.hotkeys[hotkey].keys = definitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
addSubKeyDefinition<HotkeyName extends keyof HotkeyConfig>(
|
||||||
|
hotkey: HotkeyName,
|
||||||
|
subkey: keyof HotkeyConfig[HotkeyName]['subKeys'],
|
||||||
|
definition: HotkeyDefinitionType,
|
||||||
|
) {
|
||||||
|
this.hotkeys[hotkey].subKeys[subkey as string].push(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSubKeyDefinition<HotkeyName extends keyof HotkeyConfig>(
|
||||||
|
hotkey: HotkeyName,
|
||||||
|
subkey: keyof HotkeyConfig[HotkeyName]['subKeys'],
|
||||||
|
definition: HotkeyDefinitionType,
|
||||||
|
) {
|
||||||
|
const { subKeys } = this.hotkeys[hotkey];
|
||||||
|
subKeys[subkey as string] = subKeys[subkey as string].filter((key) => !HotkeyManager.isEqual(key, definition));
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubKeyDefinitions<HotkeyName extends keyof HotkeyConfig>(
|
||||||
|
hotkey: HotkeyName,
|
||||||
|
subkey: keyof HotkeyConfig[HotkeyName]['subKeys'],
|
||||||
|
definitions: HotkeyDefinitionType[],
|
||||||
|
) {
|
||||||
|
this.hotkeys[hotkey].subKeys[subkey as string] = definitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig() {
|
||||||
|
return this.hotkeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addKeyListeners() {
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
this.keyPressedMap.set(e.key.toLowerCase(), true);
|
||||||
|
this.checkHotkeys(e, 'keydown');
|
||||||
|
});
|
||||||
|
window.addEventListener('keyup', (e) => {
|
||||||
|
// Check first as afterwards the keys are not set
|
||||||
|
this.checkHotkeys(e, 'keyup');
|
||||||
|
this.keyPressedMap.set(e.key.toLowerCase(), false);
|
||||||
|
if (e.key === 'Meta') {
|
||||||
|
this.keyPressedMap.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkHotkeyDefinition(keyDefinition: HotkeyDefinitionType, event: KeyboardEvent): AffectedEnum {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return AffectedEnum.NOT_FULL_FILLED;
|
||||||
|
}
|
||||||
|
if (keyDefinition.ignoreFormElements !== false && HotkeyManager.isFormElement(event.target)) {
|
||||||
|
return AffectedEnum.NOT_FULL_FILLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyDefinition.keys.some((key) => this.keyPressedMap.get(key) !== true)) {
|
||||||
|
return AffectedEnum.NOT_FULL_FILLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
specialKeys.some(
|
||||||
|
(key) => keyDefinition[key] !== undefined && this.keyPressedMap.get(key) !== keyDefinition[key],
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return AffectedEnum.NOT_FULL_FILLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if key is inside keyDefinition
|
||||||
|
const changedKey = event.key.toLowerCase();
|
||||||
|
if (!keyDefinition.keys.includes(changedKey) && !(changedKey in keyDefinition)) {
|
||||||
|
return AffectedEnum.FULL_FILLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AffectedEnum.FULL_FILLED_AND_CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getKeyReducer(event: KeyboardEvent) {
|
||||||
|
return (acc: AffectedEnum, key: HotkeyDefinitionType) => {
|
||||||
|
if (acc === AffectedEnum.FULL_FILLED_AND_CHANGED) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
return Math.max(acc, this.checkHotkeyDefinition(key, event));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkHotkeys(event: KeyboardEvent, type: 'keydown' | 'keyup') {
|
||||||
|
ObjectHelper.values(this.hotkeys).forEach((hotkey) => {
|
||||||
|
const isAffected = hotkey.keys.reduce(this.getKeyReducer(event), AffectedEnum.NOT_FULL_FILLED);
|
||||||
|
if (isAffected !== AffectedEnum.NOT_FULL_FILLED) {
|
||||||
|
const subKeys = ObjectHelper.entries(hotkey.subKeys).reduce(
|
||||||
|
(acc, [key, value]) => {
|
||||||
|
acc[key] = value.reduce(this.getKeyReducer(event), AffectedEnum.NOT_FULL_FILLED);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, AffectedEnum>,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isAffected === AffectedEnum.FULL_FILLED_AND_CHANGED ||
|
||||||
|
ObjectHelper.values(subKeys).some((value) => value === AffectedEnum.FULL_FILLED_AND_CHANGED)
|
||||||
|
) {
|
||||||
|
hotkey.callbacks.forEach((callback) => callback({ event, subKeys, type }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
39
tsconfig.json
Normal file
39
tsconfig.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"declaration": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"lib": [
|
||||||
|
"es6",
|
||||||
|
"es2016",
|
||||||
|
"es2017",
|
||||||
|
"es2021",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": false,
|
||||||
|
"strictFunctionTypes": false,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"target": "es6",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/hotkeys.ts",
|
||||||
|
"./src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"bin",
|
||||||
|
"node_modules",
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user