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

feat: first Release #3

Merged
merged 5 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
55 changes: 55 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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
118 changes: 28 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 <script src="inject.js"/> 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
30 changes: 2 additions & 28 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -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' ."
Expand All @@ -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"
},
Expand Down
6 changes: 2 additions & 4 deletions src/devOnly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare var FBN_DEBUG: boolean;
declare var FGN_DEBUG: boolean;

/**
* dev only stuff, so that I can test locally easier
Expand Down Expand Up @@ -33,8 +33,6 @@ function init() {
});
}

//debugger;
console.log("FBN_DEBUG", FBN_DEBUG);
if (FBN_DEBUG) {
if (FGN_DEBUG) {
init();
}
4 changes: 2 additions & 2 deletions src/log.ts
Original file line number Diff line number Diff line change
@@ -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) : () => {};
6 changes: 2 additions & 4 deletions src/notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ let timer: ReturnType<typeof setTimeout>;

// 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
Expand All @@ -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);
}

Expand Down
10 changes: 5 additions & 5 deletions src/sections/search-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function findSelectedFakeOption(el: HTMLSelectElement) {
return el
.closest(".filterItem")
?.querySelector<HTMLElement>(
`.fbn-custom-select > [data-value="${el.value}"]`
`.fgn-custom-select > [data-value="${el.value}"]`
);
}

Expand Down Expand Up @@ -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<HTMLElement>(".fbn-custom-select");
const fakeSelects = root.querySelectorAll<HTMLElement>(".fgn-custom-select");
fakeSelects.forEach((el) => el.remove());

const selects = root.querySelectorAll<HTMLSelectElement>("select");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -407,7 +407,7 @@ function getFirstRowItems(root: HTMLElement) {
}

function getSecondRowItems(root: HTMLElement) {
return root.querySelectorAll<HTMLElement>(".filtersList .fbn-custom-select");
return root.querySelectorAll<HTMLElement>(".filtersList .fgn-custom-select");
}

function getThirdRowItems(root: HTMLElement) {
Expand Down Expand Up @@ -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;
}
Expand Down
Loading