diff --git a/.github/on-release.yml b/.github/workflows/on-release.yml
similarity index 100%
rename from .github/on-release.yml
rename to .github/workflows/on-release.yml
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..dab2228
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,55 @@
+# Contributing
+
+# Quick start
+`npm install`
+`npm run watch`
+
+Then open `http://localhost:5001/testdata`.
+
+# Running against fightcade
+
+To launch Fightcade2 (an Electron app) and be able to run the developer tools, run:
+```
+/Applications/FightCade2.app/Contents/MacOS/Fightcade2 --remote-debugging-port=8315
+```
+
+I downloaded all the pages manually, so that developing is faster.
+Also added an import to `inject.js`, ideally we would use https://github.com/tapio/live-server/issues/338 instead
+
+To test I symlink
+```
+ln -s /Users/eduardo/projects/keyboard-navigation-playground/inject.js /Applications/FightCade2.app/Contents/Resources/app/inject/inject.js
+```
+
+# Testing
+There are playwright tests under the `tests` directory. They test the static HTML files,
+which are faster in most cases, but ideally we would test against a real Fightcade instance.
+
+# Recipes
+
+## Debugging the application on steam deck
+
+1. Create a `Fightcade_debug.desktop` file
+```
+!/usr/bin/env xdg-open
+[Desktop Entry]
+Version=1.0
+Type=Application
+Terminal=false
+Exec=/home/deck/Documents/Fightcade/Fightcade2.sh --remote-debugging-port=8315
+Name=Fightcade Debug
+Comment=Fightcade
+Categories=Game;Emulator;ArcadeGame
+Icon=/home/deck/Documents/Fightcade/fc2-electron/resources/app/icon.png
+~
+```
+
+2. Open desktop mode, open fightcade_debug
+
+3. Make a ssh tunnel (from your pc)
+
+```
+ssh -L 8315:localhost:8315 deck@steamdeck
+```
+
+4. Open `http://localhost:8315` in chrome
diff --git a/README.md b/README.md
index b10ed29..479afc9 100644
--- a/README.md
+++ b/README.md
@@ -1,100 +1,38 @@
-# Fightcade Keyboard Navigation
+# Fightcade Gamepad Navigation
-The objective is to be able to control the FightCade frontend (not the emulator part,
-like setting Inputs, since that's too hard),
-entirely with a Arcade Stick.
+Control the [Fightcade](https://www.fightcade.com/) frontend with a gamepad (or a keyboard).
-The approach is to make everything more keyboard accessible, and then translate the stick
-controls into keyboard events.
+https://github.com/eh-am/fightcade-gamepad-navigation/assets/6951209/40c2b094-30bb-4f95-9e02-0f6e204baed9
-# Meta
-To run Fightcade2 (an Electron app) and be able to run the developer tools, run:
-```
-/Applications/FightCade2.app/Contents/MacOS/Fightcade2 --remote-debugging-port=8315
-```
+# Motivation
+I have Steam Deck docked and plugged to a TV and an [Arcade Stick](https://www.8bitdo.com/arcade-stick/),
+so I wanted to control the Fightcade frontend with only the stick, without requiring a keyboard/mouse.
-I downloaded all the pages manually, so that developing is faster.
-Also added an import to `inject.js`, ideally we would use https://github.com/tapio/live-server/issues/338 instead
+# How to use
+## Installation
+Download the latest Release.
-To test I symlink
-```
-ln -s /Users/eduardo/projects/keyboard-navigation-playground/inject.js /Applications/FightCade2.app/Contents/Resources/app/inject/inject.js
-```
+Then copy the `inject.js` app to "INJECT_JS_PATH", which depends on the OS/way of installation:
+* On steam deck (no flatpak), it's under `/home/deck/Documents/Fightcade/fc2-electron/resources/app/inject/`
+* On a mac, it's under `/home/deck/Documents/Fightcade/fc2-electron/resources/app/inject/`
+## Running
+Open Fightcade, then press any button for the plugin to recognize the controller,
+a notification message should tell the controller has been recognized.
-# TODO:
-- [ ] login page
-- [ ] search page, hidden gems is not working
-- [ ] when focusing manually, set scroll into
-- [ ] handle asynchronousness, ex when a lobby is added to the sidebar
-- [ ] inject custom js, like the in dev mode
-- [ ] handle stick (axis) https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/axes
-- [x] use flexbox instead of grid? .wrapper: { display: flex, flexwrap }, children: { flex: 1 0 6rem; }
-- [x] add bundling
-- [ ] reduce number of event listeners, ideally one per category?
-- [ ] handle mix and matching keyboard focus and hover
-- [ ] make circular list take an element, not an index
-- [ ] focus on join first
-- [ ] upon joining, also enter lobby
-- [ ] in search there's a bug where multiple items may ge tfocus
-- [ ] make outside links unclickable, or give a warning
-- [ ] upon clicking "JOIN" | "FAV", bring focus back
-- [ ] pressing escape should go to next tabbable parent?
-- [X] prepare a teardown, so that dynamic movement works
-- [ ] TESTS: if upon changing the dom (to trigger delete an item), it still works
-- [ ] DEV: add an easy way to kill an item (to trigger a DOM reload)
-- [ ] create a custom select, since we cant trigger it manually :(
-- [ ] bug: sometimes pagination gets two items with tabindex == 0
-- [ ] BUG: welcome page stops working after doing a search :\
-- [ ] do tab roving for the filters, clear them and default to input when they are collapsed
-- [ ] implement escape
-- [ ] I had to export controller.js manually, ideally the bundler should be able to handle it
-- [ ] support left analog
-- [ ] support hold button
-- [ ] in search welcome page, allow navigating with arrowkeys instead of tab
-- [ ] add arrow to filter select
-- [ ] react to spacebar in select filter
-- [ ] disable all external links
-- [ ] user button not working when clicked
-- [ ] support navigating with only arrow keys
-- [ ] outline of joined lobby on sidebar is not clear
-- [ ] upon clicking the search icon, it should clear the filters?
-- [ ] support navigating notifications
-- [ ] support "right clicking" a user and ignore her
+Then navigate with the D-pad (or equivalent).
+By default, BUTTON_1 is translated to "Enter", and BUTTON_3 and BUTTON_4 as
+Shift+Tab and Tab, respectively.
-# Pages
+# Caveats
+* It only handles the frontend, ie not the emulators part. So initial setup for the inputs still requires a mouse/keyboard.
+* It relies on the DOM structure, so if it ever changes, it will break this plugin and will require updates. PRs welcome :)
-- Search
-- Search Result
-- All Games -> open when you search for an empty string
-- Lobby
+# Strategy
+The approach is to make everything more keyboard accessible, and then translate gamepad
+inputs to keyboard events.
-
-# Recipees
-
-## Debugging the application on deck
-
-1. Create a Fightcade_debug.desktop
-```
-!/usr/bin/env xdg-open
-[Desktop Entry]
-Version=1.0
-Type=Application
-Terminal=false
-Exec=/home/deck/Documents/Fightcade/Fightcade2.sh --remote-debugging-port=8315
-Name=Fightcade Debug
-Comment=Fightcade
-Categories=Game;Emulator;ArcadeGame
-Icon=/home/deck/Documents/Fightcade/fc2-electron/resources/app/icon.png
-~
-```
-
-2. Open desktop mode, open fightcade_bebug
-
-3. Make a ssh tunnel (from your pc)
-
-```
-ssh -L 8315:localhost:8315 deck@steamdeck
-```
-
-4. Open `http://localhost:8315` in chrome
+# Acknowledgments
+* [blueminder/fightcade-joystick-kb-controls](https://github.com/blueminder/fightcade-joystick-kb-controls/tree/main), for the initial inspiratino
+* [Controller.js](https://samiare.github.io/Controller.js/), for handling the gamepad inputs
+* [ally.js](https://allyjs.io/), for helping handling tab navigation and debugging focusable elements
diff --git a/package-lock.json b/package-lock.json
index e13ca27..45eda20 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,17 +1,16 @@
{
"name": "fightcade-better-navigation",
- "version": "1.0.0",
+ "version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "fightcade-better-navigation",
- "version": "1.0.0",
+ "version": "0.0.0",
"license": "MIT",
"dependencies": {
"ally.js": "^1.4.1",
"concurrently": "^8.2.2",
- "gamepad.js": "github:neogeek/gamepad.js",
"live-server": "^1.2.2",
"typescript": "^5.3.3"
},
@@ -634,11 +633,6 @@
"file-uri-to-path": "1.0.0"
}
},
- "node_modules/bowser": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.10.0.tgz",
- "integrity": "sha512-OCsqTQboTEWWsUjcp5jLSw2ZHsBiv2C105iFs61bOT0Hnwi9p7/uuXdd7mu8RYcarREfdjNN+8LitmEHATsLYg=="
- },
"node_modules/braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
@@ -1232,14 +1226,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/gamepad.js": {
- "version": "1.0.0",
- "resolved": "git+ssh://git@github.com/neogeek/gamepad.js.git#25787c3cab4d5ea6f9b1dc330df22ab8e64e16db",
- "license": "MIT",
- "dependencies": {
- "bowser": "2.10.0"
- }
- },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -3143,11 +3129,6 @@
"file-uri-to-path": "1.0.0"
}
},
- "bowser": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.10.0.tgz",
- "integrity": "sha512-OCsqTQboTEWWsUjcp5jLSw2ZHsBiv2C105iFs61bOT0Hnwi9p7/uuXdd7mu8RYcarREfdjNN+8LitmEHATsLYg=="
- },
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
@@ -3605,13 +3586,6 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
- "gamepad.js": {
- "version": "git+ssh://git@github.com/neogeek/gamepad.js.git#25787c3cab4d5ea6f9b1dc330df22ab8e64e16db",
- "from": "gamepad.js@github:neogeek/gamepad.js",
- "requires": {
- "bowser": "2.10.0"
- }
- },
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
diff --git a/package.json b/package.json
index a9db474..69c716b 100644
--- a/package.json
+++ b/package.json
@@ -1,16 +1,16 @@
{
"name": "fightcade-better-navigation",
- "version": "1.0.0",
+ "version": "0.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "npx playwright test --project=chromium",
"test:ui": "npx playwright test --ui --project=chromium",
"test:watch": "PWTEST_WATCH=1 npx playwright test --project=chromium",
- "build": "esbuild --define:FBN_DEBUG=false --bundle src/inject.ts --outfile=out/inject.js",
- "build:test": "esbuild --define:FBN_DEBUG=true --bundle src/inject.ts --outfile=out/inject.js",
+ "build": "esbuild --define:FGN_DEBUG=false --bundle src/inject.ts --outfile=out/inject.js",
+ "build:test": "esbuild --define:FGN_DEBUG=true --bundle src/inject.ts --outfile=out/inject.js",
"type-check": "tsc --noEmit --p tsconfig.json",
- "watch": "esbuild --watch --define:FBN_DEBUG=true --bundle src=src/inject.ts --outfile=out/inject.js",
+ "watch": "esbuild --watch --define:FGN_DEBUG=true --bundle src=src/inject.ts --outfile=out/inject.js",
"live-reload": "live-server --port=5001 --no-browser --host=0.0.0.0 --quiet --watch='out, *.html' .",
"start": "concurrently 'npm run watch' 'npm run live-reload'",
"test:start-server": "live-server --port=5002 --no-browser --host=127.0.0.1 --quiet --watch='noop' ."
@@ -21,7 +21,6 @@
"dependencies": {
"ally.js": "^1.4.1",
"concurrently": "^8.2.2",
- "gamepad.js": "github:neogeek/gamepad.js",
"live-server": "^1.2.2",
"typescript": "^5.3.3"
},
diff --git a/src/devOnly.ts b/src/devOnly.ts
index 4663f4e..bb05724 100644
--- a/src/devOnly.ts
+++ b/src/devOnly.ts
@@ -1,4 +1,4 @@
-declare var FBN_DEBUG: boolean;
+declare var FGN_DEBUG: boolean;
/**
* dev only stuff, so that I can test locally easier
@@ -33,8 +33,6 @@ function init() {
});
}
-//debugger;
-console.log("FBN_DEBUG", FBN_DEBUG);
-if (FBN_DEBUG) {
+if (FGN_DEBUG) {
init();
}
diff --git a/src/log.ts b/src/log.ts
index c2b0dd3..42c919e 100644
--- a/src/log.ts
+++ b/src/log.ts
@@ -1,8 +1,8 @@
-declare var FBN_DEBUG: boolean;
+declare var FGN_DEBUG: boolean;
// Although it's possible to drop all console logs
// I prefer to use my own logger, I case I want to handle things differently
// https://github.com/evanw/esbuild/issues/28#issuecomment-654016040
// Also bind to the original console, so that we can see the log lines
// https://stackoverflow.com/a/24209742
-export const log = FBN_DEBUG ? console.log.bind(console) : () => {};
+export const log = FGN_DEBUG ? console.log.bind(console) : () => {};
diff --git a/src/notify.ts b/src/notify.ts
index 15945de..9923482 100644
--- a/src/notify.ts
+++ b/src/notify.ts
@@ -4,7 +4,6 @@ let timer: ReturnType;
// TODO: in dev mode show an inintrusive modal
export function notify(msg: string) {
- console.log("notifying", msg);
if (isElectron()) {
// Notice that if doesn't work, you need to:
// Enable it. For ex, on mac it's under System Preferences -> Notifications
@@ -16,12 +15,11 @@ export function notify(msg: string) {
}
} else {
// Very naive solution, but since it only applies to local dev it should be fine
- let snackbar = document.getElementById("#fbn-snackbar");
+ let snackbar = document.getElementById("#fgn-snackbar");
- console.log("snackbar", snackbar);
if (!snackbar) {
snackbar = document.createElement("div");
- snackbar.setAttribute("id", "#fbn-snackbar");
+ snackbar.setAttribute("id", "#fgn-snackbar");
document.body.appendChild(snackbar);
}
diff --git a/src/sections/search-header.ts b/src/sections/search-header.ts
index bb516e6..e9888b6 100644
--- a/src/sections/search-header.ts
+++ b/src/sections/search-header.ts
@@ -5,7 +5,7 @@ function findSelectedFakeOption(el: HTMLSelectElement) {
return el
.closest(".filterItem")
?.querySelector(
- `.fbn-custom-select > [data-value="${el.value}"]`
+ `.fgn-custom-select > [data-value="${el.value}"]`
);
}
@@ -88,7 +88,7 @@ export function updateSearchHeader(root: HTMLElement) {
// We just delete the root node of the custom select
// TODO: apparently the listeners continue existing?
// https://stackoverflow.com/a/76239226
- const fakeSelects = root.querySelectorAll(".fbn-custom-select");
+ const fakeSelects = root.querySelectorAll(".fgn-custom-select");
fakeSelects.forEach((el) => el.remove());
const selects = root.querySelectorAll("select");
@@ -164,7 +164,7 @@ function setupSelect(el: HTMLSelectElement) {
//
function newFakeSelect(): HTMLElement {
const newSelect = document.createElement("div");
- newSelect.className += "fbn-custom-select";
+ newSelect.className += "fgn-custom-select";
newSelect.setAttribute("tabIndex", "-1");
// Copied from the original select
@@ -407,7 +407,7 @@ function getFirstRowItems(root: HTMLElement) {
}
function getSecondRowItems(root: HTMLElement) {
- return root.querySelectorAll(".filtersList .fbn-custom-select");
+ return root.querySelectorAll(".filtersList .fgn-custom-select");
}
function getThirdRowItems(root: HTMLElement) {
@@ -474,7 +474,7 @@ function moveHorizontally(
case "SECOND": {
currentFocused =
- currentFocused.closest(".fbn-custom-select") || currentFocused;
+ currentFocused.closest(".fgn-custom-select") || currentFocused;
items = getSecondRowItems(root);
break;
}