Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor : TS integration : src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts #458

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

ThatDeparted2061
Copy link
Contributor

@ThatDeparted2061 ThatDeparted2061 commented Feb 7, 2025

Fixes #414

cc @niladrix719 @JoshVarga

Summary by CodeRabbit

  • New Features

    • Introduced a standardized interface for keyboard input handling, paving the way for an even more consistent hotkey experience.
  • Refactor

    • Enhanced keyboard event processing with modernized declarations and improved type safety, resulting in more robust and maintainable functionality.

Copy link
Contributor

coderabbitai bot commented Feb 7, 2025

Walkthrough

The changes enhance the key binding simulator by introducing explicit TypeScript type annotations throughout the code. The update modernizes variable declarations by replacing var with const/let, improves function signatures with defined parameter and return types, and restructures key-related objects. Additionally, a new interface, IKeyCode, is added to formalize keyboard event handling and key comparison methods without altering the underlying functionality.

Changes

Files Change Summary
src/.../normalize/normalizer.plugin.ts Added explicit type annotations to variables (modifiers, KEY_MAP, shifted_symbols, etc.) and functions (e.g., isLower, capitalize, fn_name, num_name, update_current_modifiers, etc.); replaced var with const/let; restructured key objects and updated assignments.
src/.../normalize/normalizer.plugin.types.ts Introduced the IKeyCode interface defining methods and properties for keyboard event processing, including functions for key handling, comparison, and code translation.

Assessment against linked issues

Objective Addressed Explanation
Typescript Integration in /simulator/src files (#414)

Suggested reviewers

  • JoshVarga

Poem

I’m a nimble rabbit, typing away in delight,
Transforming code with TypeScript might.
Variables and keys in a graceful dance,
Hop by hop, our code gains a clearer stance.
Celebrating smooth changes—🐇✨ what a sight!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f347cc and d9a56f0.

📒 Files selected for processing (1)
  • src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

netlify bot commented Feb 7, 2025

Deploy Preview for circuitverse ready!

Name Link
🔨 Latest commit 65de945
🔍 Latest deploy log https://app.netlify.com/sites/circuitverse/deploys/67a5cf95daae5d0009c707eb
😎 Deploy Preview https://deploy-preview-458--circuitverse.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (2)
src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts (2)

370-399: Remove commented-out code.

The file contains a significant block of commented-out code at the end. This code appears to be unused debugging utilities. Remove it to improve code maintainability.

-// var fields = ['charCode', 'keyCode', 'which', 'type', 'timeStamp',
-//               'altKey', 'ctrlKey', 'shiftKey', 'metaKey'];
-// var types = ['keydown', 'keypress', 'keyup'];
-
-// function show_event(type) { ... }
-// function show_is_key_down(id, code, ctrl, alt, shift) { ... }
-// function update_key_downs() { ... }

370-399: Remove commented-out code.

The file contains a significant block of commented-out code that should be removed to improve maintainability. If this code is needed for debugging, consider moving it to a separate debug utility file.

-// var fields = ['charCode', 'keyCode', 'which', 'type', 'timeStamp',
-//               'altKey', 'ctrlKey', 'shiftKey', 'metaKey'];
-// var types = ['keydown', 'keypress', 'keyup'];
-
-// function show_event(type) {
-//     return function(e) {
-//         var lines = [];
-//         for(var i = 0; i < fields.length; ++i) {
-//             lines.push('<b>' + fields[i] + '</b>: ' + e[fields[i]] + '<br />');
-//         }
-//         document.getElementByI(type).innerHTML = lines.join('\n');
-//         return false;
-//     }
-// };
-
-// function show_is_key_down(id, code, ctrl, alt, shift) {
-//     document.getElementById(id + '_down').innerHTML = KeyCode.is_down({
-//         code: code,
-//         ctrl: ctrl,
-//         alt: alt,
-//         shift: shift
-//     });
-// };
-
-// function update_key_downs() {
-//     show_is_key_down('x', KeyCode.key('x'), false, false, false);
-//     show_is_key_down('shift_x', KeyCode.key('x'), false, false, true);
-//     show_is_key_down('shift_c', KeyCode.key('c'), false, false, true);
-//     show_is_key_down('ctrl_a', KeyCode.key('a'), true, false, false);
-// };
🧹 Nitpick comments (9)
src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.types.ts (3)

1-14: Add JSDoc comments to document the interface methods.

While the interface is well-structured, adding JSDoc comments would improve code maintainability by documenting:

  • Purpose and behavior of each method
  • Parameter descriptions and constraints
  • Return value descriptions
  • Example usage where applicable
 export interface IKeyCode {
+    /** Returns a new instance of IKeyCode without any conflicting key bindings */
     no_conflict: () => IKeyCode;
+    /** Generates a function key code from a number between 1 and 12 */
     fkey: (num: number) => number;
+    /** Generates a numeric keypad code from a number between 0 and 9 or special characters */
     numkey: (num: number | string) => number;
+    /** Generates a key code from the ASCII code of the first character of a string */
     key: (str: string) => number;
+    /** Checks if two key objects are equal including their modifiers */
     key_equals: (key1: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }, key2: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => boolean;
+    /** Translates a keycode to its normalized value */
     translate_key_code: (code: number) => number;
+    /** Translates a keyDown event to a normalized key event object */
     translate_event: (e: KeyboardEvent) => { code: number; shift: boolean; alt: boolean; ctrl: boolean; meta: boolean };
+    /** Updates internal state when a key is pressed */
     key_down: (e: KeyboardEvent) => void;
+    /** Updates internal state when a key is released */
     key_up: (e: KeyboardEvent) => void;
+    /** Returns true if the key spec is currently held down */
     is_down: (key: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => boolean;
+    /** Returns a string representation of a key event suitable for shortcuts */
     hot_key: (key: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => string;
     [key: string]: any;
 }

1-14: LGTM! Consider these improvements for better type safety.

The interface is well-structured, but could benefit from these enhancements:

  1. Consider making the index signature more specific to known key constants.
  2. Extract the repeated key object type into a reusable interface.
+interface IKeyObject {
+    code: number;
+    ctrl: boolean;
+    alt: boolean;
+    shift: boolean;
+    meta: boolean;
+}
+
 export interface IKeyCode {
     no_conflict: () => IKeyCode;
     fkey: (num: number) => number;
     numkey: (num: number | string) => number;
     key: (str: string) => number;
-    key_equals: (key1: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }, key2: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => boolean;
+    key_equals: (key1: IKeyObject, key2: IKeyObject) => boolean;
     translate_key_code: (code: number) => number;
-    translate_event: (e: KeyboardEvent) => { code: number; shift: boolean; alt: boolean; ctrl: boolean; meta: boolean };
+    translate_event: (e: KeyboardEvent) => IKeyObject;
     key_down: (e: KeyboardEvent) => void;
     key_up: (e: KeyboardEvent) => void;
-    is_down: (key: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => boolean;
-    hot_key: (key: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => string;
-    [key: string]: any;
+    is_down: (key: IKeyObject) => boolean;
+    hot_key: (key: IKeyObject) => string;
+    [key: string]: number | Function;
 }

1-14: Extract repeated types into separate interfaces and add JSDoc documentation.

The interface could be improved by:

  1. Extracting the repeated key type into a separate interface
  2. Adding JSDoc documentation for better type information
+/** Interface representing a key with its modifiers */
+interface IKey {
+    code: number;
+    ctrl: boolean;
+    alt: boolean;
+    shift: boolean;
+    meta: boolean;
+}
+
+/**
+ * Interface defining keyboard input handling methods
+ */
 export interface IKeyCode {
+    /** Returns a new instance of KeyCode */
     no_conflict: () => IKeyCode;
+    /** Generates a function key code from a number */
     fkey: (num: number) => number;
+    /** Generates a numeric keypad code */
     numkey: (num: number | string) => number;
+    /** Generates a key code from ASCII code */
     key: (str: string) => number;
-    key_equals: (key1: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }, key2: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => boolean;
+    key_equals: (key1: IKey, key2: IKey) => boolean;
     translate_key_code: (code: number) => number;
-    translate_event: (e: KeyboardEvent) => { code: number; shift: boolean; alt: boolean; ctrl: boolean; meta: boolean };
+    translate_event: (e: KeyboardEvent) => IKey;
     key_down: (e: KeyboardEvent) => void;
     key_up: (e: KeyboardEvent) => void;
-    is_down: (key: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => boolean;
-    hot_key: (key: { code: number; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }) => string;
+    is_down: (key: IKey) => boolean;
+    hot_key: (key: IKey) => string;
     [key: string]: any;
 }
src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts (6)

73-96: Improve key mapping maintainability.

The key mapping logic is spread across multiple objects and conditionals. Consider consolidating the mappings into a single configuration object.

+const KEY_MAPPINGS = {
+  default: {
+    186: 59, // ;:
+    187: 61, // =+
+    188: 44, // ,<
+    189: 95, // -_
+    190: 62, // .>
+    191: 47, // /?
+    192: 126, // `~
+    219: 91, // {[
+    220: 92, // \|
+    221: 93, // }]
+  },
+  shifted: {
+    58: 59, // : -> ;
+    43: 61, // = -> +
+    // ... rest of shifted_symbols
+  },
+  opera: {
+    // ... OPERA_KEYMAP entries
+  }
+};
+
+function initializeKeyMap(): void {
+  Object.assign(KEY_MAP, KEY_MAPPINGS.default);
+  
+  if (is_opera && is_windows) {
+    Object.assign(KEY_MAP, KEY_MAPPINGS.opera);
+  } else if (is_opera || is_konqueror || is_icab) {
+    Object.assign(KEY_MAP, KEY_MAPPINGS.opera);
+    Object.assign(KEY_MAP, KEY_MAPPINGS.shifted);
+  }
+}

Also applies to: 113-126, 128-146


106-146: Consider using a more modern browser detection approach.

The current browser detection relies on user agent strings, which is not recommended for modern web applications.

Consider using feature detection instead:

const is_gecko = typeof document.mozCancelFullScreen !== 'undefined';
const is_ie = typeof (document as any).documentMode !== 'undefined';
const is_opera = typeof (window as any).opera !== 'undefined';

200-212: Consider using readonly properties for immutable state.

The current keys state object could benefit from readonly properties to prevent accidental mutations.

 const current_keys: {
-    codes: { [key: number]: number };
-    ctrl: boolean;
-    alt: boolean;
-    shift: boolean;
-    meta: boolean;
+    readonly codes: { [key: number]: number };
+    readonly ctrl: boolean;
+    readonly alt: boolean;
+    readonly shift: boolean;
+    readonly meta: boolean;
 }

350-351: Use type-safe property access.

The current approach using keyof typeof key could be more specific.

-key[modifier as keyof typeof key]
+key[modifier as 'ctrl' | 'alt' | 'shift' | 'meta']

366-368: Consider using a type-safe way to add key constants.

The current approach of dynamically adding properties might cause type issues.

-for (const code in key_names) {
-    KeyCode[key_names[code]] = Number(code);
+Object.entries(key_names).forEach(([code, name]) => {
+    (KeyCode as any)[name] = Number(code);
+});

73-96: Extract magic numbers into named constants.

The shifted symbols mapping contains several magic numbers that could be extracted into named constants for better readability and maintainability.

+const KEY_CODES = {
+    SEMICOLON: 59,
+    EQUALS: 61,
+    COMMA: 44,
+    MINUS: 45,
+    PERIOD: 46,
+    SLASH: 47,
+    BACKTICK: 192,
+    BACKSLASH: 92,
+    QUOTE: 222,
+    // ... add more constants
+} as const;

 const shifted_symbols: { [key: number]: number } = {
-    58: 59, // : -> ;
-    43: 61, // = -> +
+    58: KEY_CODES.SEMICOLON, // : -> ;
+    43: KEY_CODES.EQUALS, // = -> +
     // ... update remaining entries
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0b26c9 and 2f347cc.

📒 Files selected for processing (2)
  • src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts (5 hunks)
  • src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.types.ts (1 hunks)
👮 Files not reviewed due to content moderation or server errors (2)
  • src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.types.ts
  • src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts
🔇 Additional comments (4)
src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts (4)

235-363: LGTM! Well-structured implementation of IKeyCode interface.

The implementation is thorough and includes proper type annotations for all methods. The code handles various edge cases and provides clear functionality for key code translation and event handling.


71-96: LGTM! Constants are well-typed and organized.

The type annotations for constants improve code clarity and maintainability.


230-233: Verify window.KeyCode type definition.

The code assumes window.KeyCode exists but TypeScript might not recognize it.

Add a type declaration:

declare global {
    interface Window {
        KeyCode?: IKeyCode;
    }
}
✅ Verification successful

🌐 Web query:

What's the recommended way to extend the Window interface in TypeScript?

💡 Result:

To properly extend the `Window` interface in TypeScript while maintaining type safety, there are three recommended approaches:

## 1. Global Declaration Merging (`.ts`/`.tsx` files)
Use **declaration merging** within a module context:
```typescript
declare global {
  interface Window {
    customProperty: string;
    customMethod: () => void;
  }
}

// Usage
window.customProperty = "TypeScript";
window.customMethod = () => console.log("Method called");
```
This approach works best when extending types within application code files[1][6].

## 2. Dedicated Declaration File (`.d.ts`)
Create a separate type definition file (e.g., `globals.d.ts`):
```typescript
interface Window {
  gtag: (...args: any[]) => void;
  dataLayer: Record<string, any>;
}
```
No need for `declare global` in `.d.ts` files as they're automatically included in global scope[5][6]. This is considered the cleanest solution for most projects.

## 3. Module-Specific Declaration
For localized type extensions within a single module:
```typescript
declare const window: typeof globalThis & {
  temporaryDebugVar: string;
};

window.temporaryDebugVar = "debug_value";
```
This approach avoids modifying global types but requires repetition across files[6].

**Best Practices:**
- Use **unique property names** (e.g., app-specific prefixes) to avoid conflicts[1]
- Prefer `.d.ts` files for organization and scalability[5][6]
- Avoid `any` type assertions (`(window as any)`) to maintain type safety[4]
- Consider namespace objects instead of direct window extensions for complex implementations[1]

**Example Configuration Pattern:**
```typescript
// globals.d.ts
interface Window {
  appConfig: {
    apiEndpoint: string;
    analyticsEnabled: boolean;
  };
}

// Implementation
window.appConfig = {
  apiEndpoint: "https://api.example.com",
  analyticsEnabled: true
};
```

These methods ensure proper TypeScript type checking while enabling safe integration with third-party libraries and global browser APIs[3][6]. For Next.js/Vite projects, place declarations in `globals.d.ts` or similar project-specific type definition files[4][5].

Citations:

- 1: https://www.squash.io/tutorial-extending-window-object-in-typescript/
- 2: https://typescriptcenter.com/expanding-the-window-setting-new-properties-on-window-in-typescript-c0f262561ae6?gi=9bdc65d66eea
- 3: https://dev.to/emmanuelgautier/extend-window-type-with-typescript-p9d
- 4: https://nono.ma/extend-window-interface-typescript-react-vite
- 5: https://www.jamestharpe.com/typescript-extend-window/
- 6: https://www.totaltypescript.com/how-to-properly-type-window
- 7: https://codemia.io/knowledge-hub/path/how_do_you_explicitly_set_a_new_property_on_window_in_typescript
- 8: https://snippets.khromov.se/extending-window-and-document-global-objects-in-typescript/

Action Required: Extend the Window interface to include KeyCode for proper type checking.

The web query confirms that the recommended approach is to utilize global declaration merging or create a dedicated declaration file. For instance, adding the following in a global declaration (e.g., in a .d.ts file) informs TypeScript about the existence and type of KeyCode on the window object:

declare global {
  interface Window {
    KeyCode?: IKeyCode;
  }
}

This will ensure that TypeScript recognizes window.KeyCode and maintains type safety throughout the codebase.


231-233: Verify window.KeyCode type safety.

The code assumes window.KeyCode exists but TypeScript might not recognize this property on the Window interface.

Add proper type declarations:

+declare global {
+    interface Window {
+        KeyCode?: IKeyCode;
+    }
+}

 let _KeyCode: IKeyCode;
 if (typeof window.KeyCode !== 'undefined') {
     _KeyCode = window.KeyCode;
 }
✅ Verification successful

Type Declaration for window.KeyCode Missing – Please Add Global Declaration

Your review comment is valid. The TypeScript compiler does not include a definition for window.KeyCode by default, which means that using it without an appropriate extension to the Window interface can lead to type-safety issues. The suggested change to add a global declaration:

declare global {
    interface Window {
        KeyCode?: IKeyCode;
    }
}

will correctly inform the TypeScript compiler about the potential existence (or absence) of KeyCode on window, ensuring that your code properly checks for its presence and handles the type correctly.

  • File impacted: src/simulator/src/hotkey_binder/model/normalize/normalizer.plugin.ts (Lines 231-233)
  • Issue: Missing declaration on the Window interface for KeyCode, leading to type unsafety.
  • Resolution: Add the provided global declaration for enhanced type safety.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Typescript Integration in /simulator/src files
1 participant