procon_bypass_man-web 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.babelrc +6 -0
  3. data/.circleci/config.yml +73 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +26 -0
  7. data/CHANGELOG.md +3 -0
  8. data/Gemfile +18 -0
  9. data/Gemfile.lock +97 -0
  10. data/LICENSE.txt +21 -0
  11. data/Procfile +2 -0
  12. data/README.md +43 -0
  13. data/Rakefile +4 -0
  14. data/bin/console +15 -0
  15. data/bin/pbm_web +7 -0
  16. data/bin/setup +8 -0
  17. data/jest.config.ts +194 -0
  18. data/lib/procon_bypass_man/web.rb +20 -0
  19. data/lib/procon_bypass_man/web/db.rb +33 -0
  20. data/lib/procon_bypass_man/web/migration/001_create_settings_table.sql +4 -0
  21. data/lib/procon_bypass_man/web/models/base_model.rb +47 -0
  22. data/lib/procon_bypass_man/web/models/setting.rb +22 -0
  23. data/lib/procon_bypass_man/web/public/bundle.js +2 -0
  24. data/lib/procon_bypass_man/web/public/bundle.js.LICENSE.txt +57 -0
  25. data/lib/procon_bypass_man/web/public/index.html +1 -0
  26. data/lib/procon_bypass_man/web/server.rb +139 -0
  27. data/lib/procon_bypass_man/web/setting_parser.rb +190 -0
  28. data/lib/procon_bypass_man/web/storage.rb +25 -0
  29. data/lib/procon_bypass_man/web/version.rb +7 -0
  30. data/package.json +48 -0
  31. data/procon_bypass_man-web.gemspec +36 -0
  32. data/src/app.tsx +5 -0
  33. data/src/components/button_setting.tsx +142 -0
  34. data/src/components/buttons_modal.tsx +110 -0
  35. data/src/components/buttons_setting.tsx +67 -0
  36. data/src/components/installable_macros.tsx +58 -0
  37. data/src/components/installable_modes.tsx +57 -0
  38. data/src/components/macro_settings.tsx +85 -0
  39. data/src/components/mode_settings.tsx +62 -0
  40. data/src/contexts/buttons_setting.ts +2 -0
  41. data/src/index.html +11 -0
  42. data/src/lib/button_state.test.ts +110 -0
  43. data/src/lib/button_state.ts +52 -0
  44. data/src/lib/button_state_diff.test.ts +123 -0
  45. data/src/lib/button_state_diff.ts +63 -0
  46. data/src/lib/buttons_setting_converter.test.ts +185 -0
  47. data/src/lib/buttons_setting_converter.ts +107 -0
  48. data/src/lib/http_client.ts +93 -0
  49. data/src/pages/bpm_page.tsx +92 -0
  50. data/src/pages/buttons_setting_page.tsx +281 -0
  51. data/src/pages/global_setting_page.tsx +83 -0
  52. data/src/pages/home.tsx +17 -0
  53. data/src/pages/recoding_mode_page.tsx +15 -0
  54. data/src/pages/top.tsx +107 -0
  55. data/src/reducers/layer_reducer.ts +120 -0
  56. data/src/types/button.ts +2 -0
  57. data/src/types/buttons_setting_type.ts +63 -0
  58. data/src/types/layer_key.ts +2 -0
  59. data/src/types/pbm_stats.ts +1 -0
  60. data/src/types/plugin.ts +43 -0
  61. data/tmp/.keep +0 -0
  62. data/tsconfig.json +75 -0
  63. data/webpack.config.js +56 -0
  64. data/yarn.lock +6815 -0
  65. metadata +150 -0
@@ -0,0 +1,83 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useEffect } from "react";
5
+ import axios from 'axios';
6
+
7
+ import { HttpClient } from "../lib/http_client";
8
+
9
+ type Prop = {
10
+ };
11
+ const httpClient = new HttpClient();
12
+
13
+ export const GlobalSetting = ({}:Prop) => {
14
+ const [dirPath, setDirPath] = useState("");
15
+ const [settingPath, setSettingPath] = useState("");
16
+ const [serverResponseMessage, setServerResponseMessage] = useState("");
17
+
18
+ const inputStyle = css`
19
+ width: 400px;
20
+ `
21
+
22
+ useEffect(() => {
23
+ httpClient.getDirPath()
24
+ .then(function (response) {
25
+ setDirPath(response.data.root_path);
26
+ })
27
+ .catch(function (error) {
28
+ setServerResponseMessage("サーバとの通信に失敗しました");
29
+ console.log(error);
30
+ })
31
+ httpClient.getSettingPath()
32
+ .then(function (response) {
33
+ setSettingPath(response.data.setting_path);
34
+ })
35
+ .catch(function (error) {
36
+ console.log(error);
37
+ })
38
+ }, [])
39
+
40
+ const handleDirChange = (e: React.ChangeEvent<HTMLInputElement>) => {
41
+ setDirPath(e.target.value);
42
+ };
43
+
44
+ const handleDirSubmit = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
45
+ if (!(e.target instanceof HTMLInputElement)) {
46
+ return;
47
+ }
48
+ httpClient.postDirPath(dirPath);
49
+ setServerResponseMessage("PBMのディレクトリパスの更新に成功しました");
50
+ };
51
+
52
+ const handleSettingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
53
+ setSettingPath(e.target.value);
54
+ };
55
+
56
+ const handleSettingSubmit = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
57
+ if (!(e.target instanceof HTMLInputElement)) {
58
+ return;
59
+ }
60
+ httpClient.postSettingPath(settingPath);
61
+ setServerResponseMessage("PBMの設定ファイルパスの更新に成功しました");
62
+ };
63
+
64
+ return (
65
+ <>
66
+ <h2>設定</h2>
67
+ <div>
68
+ {serverResponseMessage}
69
+ </div>
70
+ <label>PBMのディレクトリパス:
71
+ <input type="text" css={inputStyle} value={dirPath} onChange={handleDirChange} />
72
+ </label>
73
+ <input type="submit" value="更新する" onClick={handleDirSubmit} />
74
+ <hr />
75
+
76
+ <label>PBMの設定ファイルパス:
77
+ <input type="text" css={inputStyle} value={settingPath || "未設定"} onChange={handleSettingChange} />
78
+ </label>
79
+ <input type="submit" value="更新する" onClick={handleSettingSubmit} />
80
+ <hr />
81
+ </>
82
+ )
83
+ }
@@ -0,0 +1,17 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useEffect } from "react";
5
+
6
+ export const Home = () => {
7
+ return (
8
+ <>
9
+ <div css={css`margin-top: 30px`}>
10
+ ソースコード: <a href="https://github.com/splaplapla/procon_bypass_man-web">
11
+ https://github.com/splaplapla/procon_bypass_man-web
12
+ </a>
13
+ </div>
14
+ </>
15
+ )
16
+ }
17
+
@@ -0,0 +1,15 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useEffect } from "react";
5
+ import { HttpClient } from "../lib/http_client";
6
+
7
+ const httpClient = new HttpClient();
8
+
9
+ export const RecodingModePage = () => {
10
+ return (
11
+ <>
12
+ <div></div>
13
+ </>
14
+ )
15
+ }
data/src/pages/top.tsx ADDED
@@ -0,0 +1,107 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useReducer } from "react";
5
+ import {
6
+ BrowserRouter as Router,
7
+ Switch,
8
+ Route,
9
+ Link
10
+ } from "react-router-dom";
11
+ import { GlobalSetting } from "./global_setting_page";
12
+ import { BpmPage } from "./bpm_page";
13
+ import { Home } from "./home";
14
+ import { ButtonsSettingPage } from "./buttons_setting_page";
15
+ import { RecodingModePage } from "./recoding_mode_page";
16
+ import { ButtonsSettingContext } from "./../contexts/buttons_setting";
17
+ import { ButtonsInLayer, Layers, ButtonsSettingType } from "../types/buttons_setting_type";
18
+ import { buttons, Button } from "../types/button";
19
+ import { LayerReducer } from "../reducers/layer_reducer";
20
+
21
+ const ButtonsSettingProfile: React.FC = ({children}) => {
22
+ const initLayers: Layers = {
23
+ up: buttons.reduce((a, i) => { a[i] = { open: false }; return a }, {} as ButtonsInLayer),
24
+ right: buttons.reduce((a, i) => { a[i] = { open: false }; return a }, {} as ButtonsInLayer),
25
+ down: buttons.reduce((a, i) => { a[i] = { open: false }; return a }, {} as ButtonsInLayer),
26
+ left: buttons.reduce((a, i) => { a[i] = { open: false }; return a }, {} as ButtonsInLayer),
27
+ installed_macros: {},
28
+ installed_modes: {},
29
+ }
30
+ const [prefixKeys, setPrefixKeys] = useState([]);
31
+ const [loaded, setLoaded] = useState(false);
32
+ const [layers, layersDispatch] = useReducer(LayerReducer, initLayers as Layers);
33
+ const value = {
34
+ loaded,
35
+ setLoaded,
36
+ layers,
37
+ prefixKeys,
38
+ setPrefixKeys,
39
+ layersDispatch,
40
+ }
41
+ return (
42
+ <ButtonsSettingContext.Provider value={value}>
43
+ {children}
44
+ </ButtonsSettingContext.Provider>
45
+ )
46
+ }
47
+
48
+ export const Top: React.FC = () => {
49
+ const menuStyle = css`
50
+ ul {
51
+ list-style: none;
52
+ display:flex;
53
+ margin: 0;
54
+ padding: 0;
55
+ li {
56
+ padding-right: 3px;
57
+ display: block;
58
+ a {
59
+ text-decoration: none;
60
+ display:block;
61
+ padding:10px;
62
+ background-color: #333;
63
+ color: white;
64
+ }
65
+ }
66
+ }
67
+ `;
68
+
69
+ return (
70
+ <>
71
+ <Router>
72
+ <div>
73
+ <nav css={menuStyle}>
74
+ <ul>
75
+ <li>
76
+ <Link to="/">home</Link>
77
+ </li>
78
+ <li>
79
+ <Link to="/setting">設定</Link>
80
+ </li>
81
+ <li>
82
+ <Link to="/buttons_setting">ボタン設定</Link>
83
+ </li>
84
+ </ul>
85
+ </nav>
86
+ <Switch>
87
+ <Route exact path="/" >
88
+ <Home />
89
+ </Route>
90
+ <Route path="/setting">
91
+ <GlobalSetting />
92
+ </Route>
93
+ <Route path="/pbm">
94
+ <BpmPage />
95
+ </Route>
96
+ <Route path="/buttons_setting">
97
+ <ButtonsSettingProfile><ButtonsSettingPage /></ButtonsSettingProfile>
98
+ </Route>
99
+ <Route path="/recoding_mode">
100
+ <RecodingModePage />
101
+ </Route>
102
+ </Switch>
103
+ </div>
104
+ </Router>
105
+ </>
106
+ );
107
+ };
@@ -0,0 +1,120 @@
1
+ import { buttons, Button } from "../types/button";
2
+ import { LayerKey } from "../types/layer_key";
3
+ import { Layers, Flip, Remap, Macro, StructMacro, ModeTable, StructMode } from "../types/buttons_setting_type";
4
+
5
+ export const disableFlipType = Symbol('disableFlip');
6
+ export const alwaysFlipType = Symbol('alwaysFlip');
7
+ export const flipIfPressedSelfType = Symbol('flipIfPressedSelf');
8
+ export const flipIfPressedSomeButtonsType = Symbol('flipIfPressedSomeButtons');
9
+ export const ignoreButtonsInFlipingType = Symbol('ignoreButtonsInFliping');
10
+ export const remapType = Symbol('remap');
11
+ export const openMenuType = Symbol('openMenu');
12
+ export const closeMenuType = Symbol('closeMenu');
13
+ export const applyMacroType = Symbol('applyMacro');
14
+ export const installMacroType = Symbol('installedMacro');
15
+ export const uninstallMacroType = Symbol('uninstalledMacro');
16
+ export const installModeType = Symbol('uninstalledMacro');
17
+ export const uninstallModeType = Symbol('a')
18
+ export const applyModeType = Symbol('b')
19
+
20
+
21
+ type ACTION_TYPE =
22
+ | { type: typeof disableFlipType, payload: { layerKey: LayerKey, button: Button } }
23
+ | { type: typeof alwaysFlipType, payload: { layerKey: LayerKey, button: Button } }
24
+ | { type: typeof flipIfPressedSelfType, payload: { layerKey: LayerKey, button: Button } }
25
+ | { type: typeof flipIfPressedSomeButtonsType, payload: { layerKey: LayerKey, button: Button, targetButtons: Array<Button> } }
26
+ | { type: typeof ignoreButtonsInFlipingType, payload: { layerKey: LayerKey, button: Button, targetButtons: Array<Button> } }
27
+ | { type: typeof remapType, payload: { layerKey: LayerKey, button: Button, targetButtons: Array<Button> } }
28
+ | { type: typeof openMenuType, payload: { layerKey: LayerKey, button: Button } }
29
+ | { type: typeof closeMenuType, payload: { layerKey: LayerKey, button: Button } }
30
+ | { type: typeof applyMacroType, payload: { layerKey: LayerKey, button: (Button | undefined), macro: StructMacro } }
31
+ | { type: typeof installMacroType, payload: { layerKey: (LayerKey | undefined), button: (Button | undefined), installed_macro: string } }
32
+ | { type: typeof uninstallMacroType, payload: { layerKey: (LayerKey | undefined), button: (Button | undefined), installed_macro: string } }
33
+ | { type: typeof installModeType, payload: { layerKey: (LayerKey | undefined), button: (Button | undefined), installed_mode: string } }
34
+ | { type: typeof uninstallModeType, payload: { layerKey: (LayerKey | undefined), button: (Button | undefined), installed_mode: string } }
35
+ | { type: typeof applyModeType, payload: { layerKey: LayerKey, button: (Button | undefined), mode: StructMode } }
36
+
37
+ export const LayerReducer = (layers: Layers, action: ACTION_TYPE) => {
38
+ const layerKey = action.payload.layerKey as LayerKey;
39
+ const button = action.payload.button as Button;
40
+
41
+ const flip = layerKey && button && layers[layerKey][button]?.flip || {} as Flip
42
+ const remap = layerKey && button && layers[layerKey][button]?.remap || {} as Remap
43
+
44
+ switch (action.type) {
45
+ case disableFlipType:
46
+ flip.enable = false;
47
+ layers[layerKey][button] = { flip: flip, open: true }
48
+ return { ...layers };
49
+ case alwaysFlipType:
50
+ flip.if_pressed = [];
51
+ flip.enable = true;
52
+ layers[layerKey][button] = { flip: flip, open: true }
53
+ return { ...layers };
54
+ case flipIfPressedSelfType:
55
+ flip.if_pressed = [button];
56
+ flip.enable = true;
57
+ layers[layerKey][button] = { flip: flip, open: true }
58
+ return { ...layers };
59
+ case flipIfPressedSomeButtonsType:
60
+ flip.if_pressed = action.payload.targetButtons;
61
+ flip.enable = true;
62
+ layers[layerKey][button] = { flip: flip, open: true }
63
+ return { ...layers };
64
+ case ignoreButtonsInFlipingType:
65
+ flip.force_neutral = action.payload.targetButtons;
66
+ layers[layerKey][button] = { flip: flip, open: true }
67
+ return { ...layers };
68
+ case remapType:
69
+ flip.enable = false;
70
+ remap.to = action.payload.targetButtons;
71
+ layers[layerKey][button] = { flip: flip, remap: remap, open: true }
72
+ return { ...layers };
73
+ case openMenuType:
74
+ flip.enable = false;
75
+ layers[layerKey][button] = { flip: flip, open: true };
76
+ return { ...layers };
77
+ case closeMenuType:
78
+ flip.enable = false;
79
+ layers[layerKey][button] = { flip: flip, open: false }
80
+ return { ...layers };
81
+ case applyMacroType:
82
+ const structMacro = action.payload.macro
83
+ if(!structMacro) { return { ...layers } };
84
+ const macroTable = layers[layerKey].macro as Macro || {} as Macro
85
+ macroTable[structMacro.name] = structMacro.if_pressed.sort()
86
+ layers[layerKey].macro = macroTable
87
+ return { ...layers };
88
+ case installMacroType:
89
+ const installedMacro = action.payload.installed_macro
90
+ const h = { ...layers }
91
+ h.installed_macros ||= {}
92
+ h.installed_macros[installedMacro] = true
93
+ return h;
94
+ case uninstallMacroType:
95
+ const unregisterInstalledMacro = action.payload.installed_macro
96
+ const hh = { ...layers }
97
+ hh.installed_macros ||= {}
98
+ hh.installed_macros[unregisterInstalledMacro] = false
99
+ return hh;
100
+ case installModeType:
101
+ const installedMode = action.payload.installed_mode
102
+ const l = { ...layers }
103
+ l.installed_modes ||= {}
104
+ l.installed_modes[installedMode] = true
105
+ return l;
106
+ case uninstallModeType:
107
+ const uninstallMode = action.payload.installed_mode
108
+ const uml = { ...layers }
109
+ uml.installed_modes ||= {}
110
+ uml.installed_modes[uninstallMode] = false
111
+ return uml;
112
+ case applyModeType:
113
+ const applyMode = action.payload.mode;
114
+ layers[layerKey].mode = { [applyMode.name]: true};
115
+ return { ...layers };
116
+ default:
117
+ console.log("一致しないaction typeです")
118
+ return { ...layers };
119
+ }
120
+ };
@@ -0,0 +1,2 @@
1
+ export const buttons = ["a", "b", "x", "y", "up", "right", "down", "left", "r", "l", "zr", "zl"] as const;
2
+ export type Button = typeof buttons[number];
@@ -0,0 +1,63 @@
1
+ import { Button } from "./button";
2
+ import { LayerKey } from "./layer_key";
3
+
4
+ export type Flip = {
5
+ if_pressed?: Array<Button>,
6
+ enable: Boolean,
7
+ force_neutral?: Array<Button>,
8
+ }
9
+
10
+ export type StructMacro = {
11
+ name: string,
12
+ if_pressed: Array<Button>,
13
+ }
14
+
15
+ export type StructMode = {
16
+ name: string,
17
+ }
18
+
19
+ export type Macro = {
20
+ [key in string]: Array<Button>;
21
+ }
22
+
23
+ export type ModeTable = {
24
+ [key in string]: boolean;
25
+ }
26
+
27
+ export type Remap = {
28
+ to: Array<Button>,
29
+ }
30
+
31
+ export type ButtonInLayer = {
32
+ flip?: Flip,
33
+ macro?: Macro, // deprecated
34
+ remap?: Remap,
35
+ open: boolean,
36
+ }
37
+
38
+ type _ButtonsInLayer = {
39
+ [key in Button] : ButtonInLayer;
40
+ }
41
+
42
+ export type ButtonsInLayer = _ButtonsInLayer & {
43
+ macro?: Macro; // TODO macroTableという名前にしたい
44
+ mode?: ModeTable;
45
+ };
46
+
47
+ export type InstalledPlugin = {
48
+ [key in string]: boolean;
49
+ }
50
+
51
+ export type Layers = {
52
+ up: ButtonsInLayer,
53
+ right: ButtonsInLayer,
54
+ down: ButtonsInLayer,
55
+ left: ButtonsInLayer,
56
+ installed_macros?: InstalledPlugin,
57
+ installed_modes?: InstalledPlugin,
58
+ }
59
+
60
+ export type ButtonsSettingType = {
61
+ prefix_keys_for_changing_layer: Array<Button>;
62
+ layers: Layers;
63
+ }
@@ -0,0 +1,2 @@
1
+ export const layerKeys = ["up", "right", "down", "left"] as const;
2
+ export type LayerKey = typeof layerKeys[number];
@@ -0,0 +1 @@
1
+ export type PbmStats = "stopped" | "running" | "unknown" | "waiting" | "error";
@@ -0,0 +1,43 @@
1
+ export type PluginBody = {
2
+ display_name: string;
3
+ class_namespace: string;
4
+ }
5
+
6
+ export type Plugin = {
7
+ [key in string] : {
8
+ modes: Array<PluginBody>;
9
+ macros: Array<PluginBody>;
10
+ }
11
+ }
12
+
13
+ // plugins.
14
+ export const AvailablePlugins = [
15
+ {
16
+ splatoon2: {
17
+ modes: [
18
+ { display_name: "splatoon2.guruguru", class_namespace: "ProconBypassMan::Splatoon2::Mode::Guruguru" },
19
+ ],
20
+ macros: [
21
+ { display_name: "splatoon2.fast_return", class_namespace: "ProconBypassMan::Splatoon2::Macro::FastReturn" },
22
+ ],
23
+ }
24
+ } as Plugin,
25
+ ]
26
+
27
+ export const MacroNameMap = AvailablePlugins.reduce((hash, item: Plugin) => {
28
+ for (var [name, plugin] of Object.entries(item)) {
29
+ plugin.macros.forEach((macro: PluginBody) => {
30
+ hash[macro.class_namespace] = macro.display_name
31
+ })
32
+ };
33
+ return hash;
34
+ }, {} as any)
35
+
36
+ export const ModeNameMap = AvailablePlugins.reduce((hash, item: Plugin) => {
37
+ for (var [name, plugin] of Object.entries(item)) {
38
+ plugin.modes.forEach((mode: PluginBody) => {
39
+ hash[mode.class_namespace] = mode.display_name
40
+ })
41
+ };
42
+ return hash;
43
+ }, {} as any)