TypeScript(TS) 自定义绑定快捷键

news2024/9/27 7:18:59

 有很多软件中都可以让用户自定义快捷键

如微信中的快捷键:

思路:

1. 将快捷键分为两部分:

    a. 主要的键   'shift', 'ctrl', 'alt', 'command';

    b. 非主要的键  字母键、数字键等;

2. 键盘按下事件:比较按键和绑定的快捷键是否相同

 代码实现

/**
 * 快捷键信息对象
 */
interface Shortcuts {
    // 快捷键
    readonly shortcut:string;
    // 名称
    readonly name: string;
    // 执行的方法
    readonly callback:Function,
    // 字母键
    readonly key: string,
    // 主组合键
    readonly modifiers: string[],
}

const KEY_SHORTCUT = ['shift', 'ctrl', 'alt', 'command'];


/**
 * 快捷键
 */
export default class ShortcutBinder {

    // 存放快捷键信息的集合
    private readonly shortcuts:Shortcuts[];
    // 新快捷键  --->   默认快捷键
    private readonly defaultShortcutsMap: Map<string, string> = new Map<string, string>();
    private readonly defaultShortcuts:Shortcuts[];

    private static shortcutBinder:ShortcutBinder;

    // 单例
    public static getInstance(){
        if (!ShortcutBinder.shortcutBinder){
            ShortcutBinder.shortcutBinder = new ShortcutBinder()
        }
        return ShortcutBinder.shortcutBinder;
    }


    private constructor() {
        this.shortcuts = [];
        this.defaultShortcuts = [];

        // 初始化
        this.init();
    }

    private init() {
        this.addKeydownEvent();
    }

    /**
     * 添加快捷键
     */
    private addKeydownEvent() {
        // 全局 键盘按下事件
        document.addEventListener('keydown', (event) => {
            console.log("按键:", event.keyCode, event.code, event.key)
            const modifers = getModifierKeysByKeyboardEvent(eventInfo.event);
            const matchedShortcut = this.shortcuts.find(shortcut =>
                    // 判断字母按键是否相同
                    checkKeyMatch(shortcut.key, event.key.toLowerCase()) &&
                    // 判断非字母按键是否相同
                    checkModifiersMatch(shortcut.modifiers, modifers)
            );

            if (matchedShortcut !== undefined) {
                // 执行函数
                matchedShortcut.callback(event);
            }
        });
    }

    /**
     * 绑定默认快捷键和对应执行的方法
     * @param {string} shortcut
     * @param {Function} callback
     */
    bind(shortcut:string, name:string, callback:Function) {
        this.addShortcut(shortcut, name, callback);
        // 存放默认快捷键
        this.defaultShortcutsMap.set(shortcut, shortcut);
        // 克隆默认快捷键
        this.defaultShortcuts.push(this.shortcuts[this.shortcuts.length-1])
    }

    /**
     * 修改绑定的快捷键
     * @param newShortcut 新组合键
     * @param oldShortcut 旧组合键
     */
    editBind(newShortcut:string, oldShortcut:string){
        if (newShortcut === oldShortcut){
            // 新键和旧键一致 不做处理
            return;
        }
        let flag = false;
        // 获取快捷键
        let shortcutObj:Shortcuts|undefined = undefined;
        let len = this.shortcuts.length;
        // 当前位置
        let c = 0;
        for (let i = 0; i < len; i++) {
            if (oldShortcut === this.shortcuts[i].shortcut){
                shortcutObj = this.shortcuts[i];
                c = i;
            }
            if (newShortcut === this.shortcuts[i].shortcut){
                // 新快捷键与原有的冲突
                flag = true;
            }
            if (shortcutObj !== undefined && flag){
                break
            }
        }
        if (flag){
            // TODO 弹窗提示 快捷键冲突
            return;
        }
        if (shortcutObj === undefined){
            // TODO 弹窗提示没有该组合键
            return;
        }
        // 添加新的快捷键
        this.addShortcut(newShortcut, shortcutObj.name, shortcutObj.callback);
        // 删除原来的快捷键
        this.shortcuts.splice(c, 1);

        // 如果 旧快捷键 是默认键添加到映射
        let shortcutSlt = this.defaultShortcutsMap.get(oldShortcut);
        if (shortcutSlt !== undefined){
            // 删除原始的
            this.defaultShortcutsMap.delete(oldShortcut);
            // 添加新映射关系
            this.defaultShortcutsMap.set(newShortcut, shortcutSlt);
        }

    }

    /**
     * 重置绑定的快捷键
     * @param shortcut
     */
    resettingBind(shortcut?:string){
        if (shortcut !== undefined){
            // 重置一个组合键
            let oldShortcut = this.defaultShortcutsMap.get(shortcut);
            if (oldShortcut !== undefined){
                this.editBind(oldShortcut, shortcut);
            }
            return;
        }
        // 重置所有组合键
        this.shortcuts.length = 0;
        this.defaultShortcuts.forEach(ds => {
            this.shortcuts.push(ds);
        })

    }

    /**
     * 获取快捷键集合
     */
    getShortcuts(){
        return this.shortcuts;
    }


    /**
     * 添加绑定的快捷键
     * @param {string} shortcut 快捷键
     * @param {Function} callback 执行的方法
     */
    private addShortcut(shortcut:string, name:string, callback:Function) {
        this.shortcuts.push({
            // 快捷键
            shortcut,
            // 名称
            name,
            // 执行的方法
            callback,
            // 字母按键 注意: 字母按键 不区分大小写但event.key区分大小写
            key: this.getKeyByShortcut(shortcut),
            // 主按键  'shift', 'ctrl', 'alt', 'command'
            modifiers: this.getModifiersByShortcut(shortcut),
        });
    }


    /**
     * 获取字母按键
     * @param {string} shortcut 快捷键的组合
     * @returns {string}
     */
    private getKeyByShortcut(shortcut:string) {
        if (!shortcut.trim()) return '';
        let key = (shortcut.split('+').filter((key) => !KEY_SHORTCUT.includes(key.trim().toLowerCase()))[0] || '');
        return key.toLowerCase();
    }

    /**
     * 获取主按键 ['shift', 'ctrl', 'alt', 'command']
     * @param {string} shortcut 快捷键的组合
     * @returns {Array}
     */
    private getModifiersByShortcut(shortcut:string) {
        const keys = shortcut.split('+').map((key) => key.trim().toLowerCase());
        let modifiers:string[] = [];
        keys.forEach((key) => {
            if (KEY_SHORTCUT.includes(key)) {
                modifiers.push(key);
            }
        });

        return modifiers;
    }
}



/**
 * 统一主按键
 * @param {KeyboardEvent} event - keyboard event
 * @returns {Array}
 */
const getModifierKeysByKeyboardEvent = (event:KeyboardEvent) => {
    const modifiers = [];

    if (event.shiftKey) {
        modifiers.push('shift');
    }

    if (event.altKey) {
        modifiers.push('alt');
    }

    if (event.ctrlKey) {
        modifiers.push('ctrl');
    }

    if (event.metaKey) {
        modifiers.push('command');
    }
    return modifiers;
};


/**
 * 判断按下的非字母键是否相同
 * @param {Array} modifers1
 * @param {Array} modifers2
 * @returns {boolean}
 */
function checkModifiersMatch(modifers1:string[], modifers2:string[]) {
    return modifers1.sort().join(',') === modifers2.sort().join(',');
}

/**
 * 判断按下的 字母键 是否相同
 * @param {string} shortcutKey
 * @param {string} eventKey - event.key
 * @returns {boolean}
 */
function checkKeyMatch(shortcutKey:string, eventKey:string) {

    return shortcutKey === eventKey;
}
 使用方式:
let shortcutBinder = ShortcutBinder.getInstance(this.editCore);
 // enter 菜单快捷键
shortcutBinder.bind('enter', "快速标记菜单",() => {
  console.log('enter');
});
shortcutBinder.bind('ctrl+s', '保存', () => {
  console.log('ctrl+s');
});
注意:

由于 event.key 区分受 shift 的影响,如:字母键区分大小写;

可以考虑 event.keyCode, event.code 来代替非主键('shift', 'ctrl', 'alt', 'command')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2089468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Echarts关系图特效实现

全屏展示 鼠标经过高亮展示 点击其他节点&#xff0c;加载其他节点数据 这个主要利用了echarts的关系图配置。因为需要将相同类型的数据放一起&#xff0c;所以不能实用引力图&#xff0c;引力图虽然效果比较好&#xff0c;而且有动画&#xff0c;但是无法根据同一类型的东西在…

快递时效新视角:‌批量分析派件与签收策略

在快递行业日益竞争的今天&#xff0c;‌时效成为了衡量快递服务质量的重要指标之一。‌对于商家和消费者而言&#xff0c;‌了解快递从到达最后站点到派件以及签收的时效&#xff0c;‌对于优化物流流程、‌提升客户体验具有重要意义。‌本文将介绍如何利用快递批量查询高手软…

17-18 - make 中的路径搜索

---- 整理自狄泰软件唐佐林老师课程 文章目录 1. 常用的源码管理方式1.1 特殊的预定义变量 VPATH&#xff08;全大写&#xff09;1.2 make 对于 VPATH 值的处理方式1.3 vpath&#xff08;全小写&#xff09; 2. 常见问题2.1 问题 12.2 问题 2 1. 常用的源码管理方式 项目中的 …

【化学方程式配平 / 3】

题目 代码 #include <bits/stdc.h> using namespace std; const double eps 1e-8; unordered_map<string, int> e; int eidx, midx; //eidx 元素数&#xff0c; midx 物质数 double matrix[45][45]; int q; bool check_alpha(char c) {if(c > a && c …

这一届单机游戏玩家,都在用云电脑玩《黑神话悟空》

文 | 智能相对论 作者 | 陈泊丞 周五下班&#xff0c;上号玩游戏&#xff0c;突然发现&#xff0c;之前因为电脑配置跟不上&#xff0c;“A”了大半年的游戏亲友竟然在线&#xff1f;&#xff01; “哟&#xff0c;终于舍得配电脑了&#xff1f;&#xff01;”我发消息问道。…

RedisTemplate集成+封装RedisUtil

文章目录 1.项目搭建1.创建一个redis模块2.调整pom.xml&#xff0c;使其成为单独的模块1.sun-common-redis的pom.xml 取消parent2.sun-common的pom.xml 取消对redis模块的管理3.sun-frame的pom.xml 增加对redis模块的管理4.关于只在modules中配置子模块&#xff0c;但是子模块没…

每日OJ_牛客_数据库连接池(简单模拟)

目录 牛客_数据库连接池&#xff08;简单模拟&#xff09; 解析代码 牛客_数据库连接池&#xff08;简单模拟&#xff09; 数据库连接池__牛客网 解析代码 题目解析&#xff1a; 服务器后台访问数据库时&#xff0c;需要先连上数据库&#xff0c;而为了连上数据库&#xf…

数盟IOS端可信ID

一、基本情况介绍 数盟IOS端可信ID介绍页: 数字联盟 数盟号称是还原出原生的IDFA, 但是苹果官网这么介绍&#xff1a; 用户开启跟踪允许跟踪后&#xff0c;APP才可以请求获取IDFA&#xff0c;且用户交互界面允许后&#xff0c;APP才能获取到IDFA. 官网给出的基本架构&#xf…

文章改写神器哪个好用?4款好评不断!

在内容创作中改写文章是少不了的工作&#xff0c;而想要高效率快速的完成改写我们是需要讲究下方法。随着文章改写神器的出现&#xff0c;它已成为了许多创作者在改写文章过程中的得力助手。那么&#xff0c;在众多的选择中&#xff0c;哪些文章改写神器真正好用呢&#xff1f;…

Django+Vue花卉商城系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 需要的环境3.2 Django接口层3.3 实体类3.4 config.ini3.5 启动类3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质创作者&…

小王陪您考系统规划与管理师之监督管理必考知识点

监督管理必备 1、风险监督的基本方法 风险评估风险审计定期的风险评审差异和趋势分析技术的绩效评估预留管理 2、服务质量的特性 安全性&#xff1a;保密性、完成性、可用性可靠性&#xff08;练完有追吻&#xff09;&#xff1a;连续性、完备性、有效性、可追溯性、稳定性…

C++中的异常处理与资源管理

前言 在软件开发中&#xff0c;异常处理是确保程序健壮性和可靠性的关键机制之一。同时&#xff0c;资源管理也是至关重要的&#xff0c;尤其是在C这样的语言中&#xff0c;手动管理资源的需求较高。本文将探讨C中的异常处理机制以及如何有效地管理资源&#xff0c;以避免资源…

【Python机器学习】NLP词频背后的含义——距离和相似度

我们可以使用相似度评分&#xff08;和距离&#xff09;&#xff0c;根据两篇文档的表示向量间的相似度&#xff08;或距离&#xff09;来判断文档间有多相似。 我们可以使用相似度评分&#xff08;和举例&#xff09;来查看LSA主题模型与高维TF-IDF模型之间的一致性。在去掉了…

网络模型及协议介绍

一.OSI七层模型 OSI Open System Interconnect 开放系统互连模型 以前不同厂家所生产的网络设备的标准是不同的&#xff0c;所以为了统一生产规范就制定了OSI这个生产模型。 作用&#xff1a;降低网络进行数据通信复杂度 这个模型的作用第一降低数据通信的复杂度&#xff…

时序预测 | 基于VMD-SSA-LSSVM+LSTM多变量时间序列预测模型(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 旧时回忆&#xff0c;独此一家。基于VMD-SSA-LSSVMLSTM多变量时间序列预测模型&#xff08;Matlab&#xff09; ——————组合模型预测结果—————————— 预测绝对平均误差MAE LSTM VMDSSALSSVM 组合模型 …

Tomcat10安装

Tomcat下载 进入官网下载https://tomcat.apache.org 注意tomcat版本和Java版本的对应关系&#xff1a; 配置好JAVA_HOME 安装tomcat前&#xff0c;需要先配置好JAVA_HOME&#xff0c;因为tomcat启动时候默认会找环境里面的JAVA_HOME&#xff0c;这里选择的Java版本是java1…

桥接与NET

仔细看看下面两幅图 net模式&#xff0c;就是在你的Windows电脑&#xff08;假设叫A电脑&#xff09;的网络基础上&#xff0c;再生成一个子网络&#xff0c;ip的前两位默认就是192.168&#xff0c;然后第三位是随机&#xff0c;第四位是自己可以手动设置的。使用这种模式唯一的…

112. 路径总和(递归法)

目录 一&#xff1a;题目&#xff1a; 二&#xff1a;代码&#xff1a; 三&#xff1a;结果&#xff1a; 一&#xff1a;题目&#xff1a; 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所…

C语言基础(二十七)

1、位字段&#xff08;Bit-fields&#xff09;也是一种数据结构&#xff0c;允许在结构体&#xff08;struct&#xff09;或联合体&#xff08;union&#xff09;中定义其成员占用特定的位数。对于需要精确控制内存布局或处理硬件寄存器映射等场景非常有用。位字段使得开发者能…

leedCode - - - 动态规划

目录 1.斐波那契数列&#xff08;LeetCode 509&#xff09; 2.零钱兑换&#xff08; LeetCode 322 &#xff09; 3.爬楼梯&#xff08; LeetCode 70 &#xff09; 4.不同路径&#xff08; LeetCode 62 &#xff09; 5.最长递增子序列&#xff08;LeetCode 300&#xff09; …