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,58 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useEffect, useContext } from "react";
5
+ import { ButtonsSettingContext, } from "./../contexts/buttons_setting";
6
+ import { Plugin, PluginBody, AvailablePlugins } from "../types/plugin";
7
+ import { installMacroType, uninstallMacroType } from "../reducers/layer_reducer";
8
+
9
+ const macroClassNamespaces = AvailablePlugins.map((v) => {
10
+ return Object.entries(v).map((v) => {
11
+ const name = v[0];
12
+ const plugin = v[1];
13
+ return plugin.macros.map((m) => {
14
+ return m.class_namespace
15
+ })
16
+ })
17
+ }).flat().flat();
18
+
19
+
20
+ type Props = {
21
+ classNamespace: string;
22
+ };
23
+ export const InstallableMacro = ({ classNamespace }: Props) => {
24
+ const { layers, layersDispatch } = useContext(ButtonsSettingContext);
25
+ const isChecked = (name: string) => {
26
+ return layers.installed_macros[name] || false;
27
+ }
28
+ const handleClick = (e: React.ChangeEvent<HTMLInputElement>) => {
29
+ if(isChecked(classNamespace)) {
30
+ layersDispatch({ type: uninstallMacroType, payload: { installed_macro: classNamespace }});
31
+ } else {
32
+ layersDispatch({ type: installMacroType, payload: { installed_macro: classNamespace }});
33
+ }
34
+ }
35
+ return(
36
+ <>
37
+ <input type="checkbox" onChange={handleClick} checked={isChecked(classNamespace)} /> {classNamespace}
38
+ </>
39
+ )
40
+ }
41
+
42
+ export const InstallableMacros = () => {
43
+ return(
44
+ <>
45
+ {
46
+ macroClassNamespaces.map((classNamespace, i) => {
47
+ return(
48
+ <div key={i}>
49
+ <label>
50
+ <InstallableMacro classNamespace={classNamespace} />
51
+ </label>
52
+ </div>
53
+ );
54
+ })
55
+ }
56
+ </>
57
+ )
58
+ }
@@ -0,0 +1,57 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useEffect, useContext } from "react";
5
+ import { ButtonsSettingContext, } from "./../contexts/buttons_setting";
6
+ import { Plugin, PluginBody, AvailablePlugins } from "../types/plugin";
7
+ import { installModeType, uninstallModeType } from "../reducers/layer_reducer";
8
+
9
+ const modeClassNamespaces = AvailablePlugins.map((v) => {
10
+ return Object.entries(v).map((v) => {
11
+ const name = v[0];
12
+ const plugin = v[1];
13
+ return plugin.modes.map((m) => {
14
+ return m.class_namespace
15
+ })
16
+ })
17
+ }).flat().flat();
18
+
19
+ type Props = {
20
+ classNamespace: string;
21
+ };
22
+ export const InstallableMode = ({ classNamespace }: Props) => {
23
+ const { layers, layersDispatch } = useContext(ButtonsSettingContext);
24
+ const isChecked = (name: string) => {
25
+ return layers.installed_modes[name] || false;
26
+ }
27
+ const handleClick = (e: React.ChangeEvent<HTMLInputElement>) => {
28
+ if(isChecked(classNamespace)) {
29
+ layersDispatch({ type: uninstallModeType, payload: { installed_mode: classNamespace }});
30
+ } else {
31
+ layersDispatch({ type: installModeType, payload: { installed_mode: classNamespace }});
32
+ }
33
+ }
34
+ return(
35
+ <div>
36
+ <input type="checkbox" onChange={handleClick} checked={isChecked(classNamespace)} />{classNamespace}
37
+ </div>
38
+ )
39
+ }
40
+
41
+ export const InstallableModes = () => {
42
+ return(
43
+ <>
44
+ {
45
+ modeClassNamespaces.map((classNamespace, i) => {
46
+ return(
47
+ <div key={i}>
48
+ <label>
49
+ <InstallableMode classNamespace={classNamespace} />
50
+ </label>
51
+ </div>
52
+ );
53
+ })
54
+ }
55
+ </>
56
+ )
57
+ }
@@ -0,0 +1,85 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useContext } from "react";
5
+ import { ButtonsSettingContext } from "./../contexts/buttons_setting";
6
+ import { LayerKey } from "../types/layer_key";
7
+ import { Button } from "../types/button";
8
+ import { Macro, StructMacro } from "../types/buttons_setting_type";
9
+ import { Plugin, PluginBody, AvailablePlugins, MacroNameMap } from "../types/plugin";
10
+ import { ButtonsModal } from "./buttons_modal";
11
+ import { applyMacroType } from "../reducers/layer_reducer";
12
+
13
+ type MacroSettingProps = {
14
+ layerKey: LayerKey;
15
+ macro: StructMacro;
16
+ };
17
+ const MacroSetting = ({ macro, layerKey }: MacroSettingProps) => {
18
+ const { layersDispatch } = useContext(ButtonsSettingContext);
19
+ // for modal
20
+ const [openModal, setOpenModal] = useState(false)
21
+ const [modalCallbackOnSubmit, setModalCallbackOnSubmit] = useState(undefined as any)
22
+ const [modalCloseCallback, setModalCloseCallback] = useState(undefined as any)
23
+ const [modalTitle, setModalTitle] = useState("")
24
+ const [modalPrefillButtons, setModalPrefillButtons] = useState<Array<Button>>([])
25
+
26
+ const setButtonsForModal = (bs: Array<Button>) => {
27
+ macro.if_pressed = bs;
28
+ layersDispatch({ type: applyMacroType, payload: { layerKey: layerKey, macro: macro }});
29
+ }
30
+ const handleClick = (e: React.ChangeEvent<HTMLInputElement>) => {
31
+ setOpenModal(true)
32
+ setModalTitle("発動キーの設定")
33
+ setModalPrefillButtons(macro.if_pressed);
34
+ setModalCallbackOnSubmit(() => setButtonsForModal);
35
+ setModalCloseCallback(() => setOpenModal);
36
+ }
37
+ const isEnable = macro.if_pressed.length > 0;
38
+
39
+ return(
40
+ <>
41
+ <li key={macro.name}>
42
+ <label>
43
+ <input type="checkbox" onChange={handleClick} checked={isEnable} />
44
+ {MacroNameMap[macro.name]}
45
+ </label>
46
+ <br />
47
+ {isEnable && `${macro.if_pressed.join(", ")}で発動`}
48
+ </li>
49
+ <div css={css`position: relative;`}>
50
+ {openModal && <ButtonsModal callbackOnSubmit={modalCallbackOnSubmit} callbackOnClose={modalCloseCallback} title={modalTitle} prefill={macro.if_pressed} positionOnShown={"relative"} />}
51
+ </div>
52
+ </>
53
+ )
54
+ }
55
+
56
+ type MacroSettingsProps = {
57
+ layerKey: LayerKey;
58
+ };
59
+ export const MacroSettings = ({ layerKey }:MacroSettingsProps) => {
60
+ const { layers } = useContext(ButtonsSettingContext);
61
+ const macroTable = layers[layerKey].macro as any || {} as any;
62
+ const macros = Object.keys(MacroNameMap).reduce((acc, macroName: string) => {
63
+ const ifp = macroTable[macroName as string] as Array<Button> || [] as Array<Button>;
64
+ if(layers.installed_macros[macroName]) {
65
+ acc.push({ name: macroName, if_pressed: ifp } as StructMacro);
66
+ }
67
+ return acc;
68
+ }, [] as Array<any>)
69
+ const hasSomeMacros = macros.length > 0;
70
+
71
+ return(
72
+ <>
73
+ {
74
+ hasSomeMacros &&
75
+ <ul>
76
+ {macros.map((m) => {
77
+ return <MacroSetting key={m.name} macro={m} layerKey={layerKey} />
78
+ }
79
+ )}
80
+ </ul>
81
+ }
82
+ {!hasSomeMacros && `選択可能なマクロがありません`}
83
+ </>
84
+ )
85
+ }
@@ -0,0 +1,62 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useContext } from "react";
5
+ import { ButtonsSettingContext } from "./../contexts/buttons_setting";
6
+ import { Plugin, PluginBody, AvailablePlugins, ModeNameMap } from "../types/plugin";
7
+ import { LayerKey } from "../types/layer_key";
8
+ import { Button } from "../types/button";
9
+ import { StructMode, ModeTable } from "../types/buttons_setting_type";
10
+ import { applyModeType } from "../reducers/layer_reducer";
11
+
12
+ type DetailProps = {
13
+ layerKey: LayerKey;
14
+ mode: StructMode;
15
+ };
16
+ export const ModeSetting = ({ layerKey, mode }: DetailProps) => {
17
+ const { layersDispatch, layers } = useContext(ButtonsSettingContext);
18
+ const handleClick = (e: React.ChangeEvent<HTMLInputElement>) => {
19
+ layersDispatch({ type: applyModeType, payload: { layerKey: layerKey, mode: mode }});
20
+ }
21
+ const isChecked = (mode: StructMode) => {
22
+ return ((layers[layerKey].mode || false) && !!layers[layerKey].mode[mode.name]);
23
+ }
24
+ return(
25
+ <li>
26
+ <label><input type="radio" onChange={handleClick} checked={isChecked(mode)} />{mode.name}</label>
27
+ </li>
28
+ )
29
+ }
30
+
31
+ type ListProps = {
32
+ layerKey: LayerKey;
33
+ };
34
+ export const ModeSettings = ({ layerKey }:ListProps) => {
35
+ const { layers } = useContext(ButtonsSettingContext);
36
+ const modeTable: ModeTable = layers[layerKey].mode || {};
37
+ const modes = Object.keys(ModeNameMap).reduce((acc, modeName: string) => {
38
+ if(layers.installed_modes[modeName]) {
39
+ acc.push({ name: modeName } as StructMode);
40
+ }
41
+ return acc;
42
+ }, [] as Array<StructMode>);
43
+ modes.unshift({ name: "disable" } as StructMode);
44
+
45
+ // const hasSomeModes = layers.modes.length > 1;
46
+ const hasSomeModes = modes.length > 1;
47
+
48
+ return(
49
+ <>
50
+ {
51
+ hasSomeModes &&
52
+ <ul>
53
+ {modes.map((m) => {
54
+ return <ModeSetting key={m.name} mode={m} layerKey={layerKey} />
55
+ }
56
+ )}
57
+ </ul>
58
+ }
59
+ {!hasSomeModes && `選択可能なモードがありません`}
60
+ </>
61
+ )
62
+ }
@@ -0,0 +1,2 @@
1
+ import { createContext } from "react";
2
+ export const ButtonsSettingContext = createContext<any>(null);
data/src/index.html ADDED
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <title><%= htmlWebpackPlugin.options.title %></title>
6
+ </head>
7
+ <body>
8
+ <h1>PBM Web</h1>
9
+ <div id="app"></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,110 @@
1
+ import { ButtonState } from "./button_state";
2
+
3
+ describe('flip, macro, remapがundefined', () => {
4
+ it('全部falseを返す', () => {
5
+ const buttonState = new ButtonState("y");
6
+
7
+ expect(buttonState.isDisabledFlip()).toBe(true);
8
+ expect(buttonState.isAlwaysFlip()).toBe(false);
9
+ expect(buttonState.isFlipIfPressedSelf()).toBe(false);
10
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(false);
11
+ expect(buttonState.hasFlipSetting()).toBe(false);
12
+ expect(buttonState.isRemap()).toBe(false);
13
+ })
14
+ });
15
+
16
+ describe('flipに値がある時', () => {
17
+ describe('enableがtrueの時', () => {
18
+ describe('if_pressedがundefinedの時', () => {
19
+ it('isAlwaysFlip()がtrueを返す', () => {
20
+ const buttonState = new ButtonState("y", { if_pressed: undefined, enable: true });
21
+ expect(buttonState.isDisabledFlip()).toBe(false);
22
+ expect(buttonState.isAlwaysFlip()).toBe(true);
23
+ expect(buttonState.isFlipIfPressedSelf()).toBe(false);
24
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(false);
25
+ expect(buttonState.hasFlipSetting()).toBe(true);
26
+ expect(buttonState.isRemap()).toBe(false);
27
+ })
28
+ })
29
+ describe('if_pressedが空配列の時', () => {
30
+ it('isAlwaysFlip()がtrueを返す', () => {
31
+ const buttonState = new ButtonState("y", { if_pressed: [], enable: true });
32
+ expect(buttonState.isDisabledFlip()).toBe(false);
33
+ expect(buttonState.isAlwaysFlip()).toBe(true);
34
+ expect(buttonState.isFlipIfPressedSelf()).toBe(false);
35
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(false);
36
+ expect(buttonState.hasFlipSetting()).toBe(true);
37
+ expect(buttonState.isRemap()).toBe(false);
38
+ })
39
+ })
40
+ describe('if_pressedに2つの値が入っている時', () => {
41
+ it('isFlipIfPressedSomeButtons()がtrueを返す', () => {
42
+ const buttonState = new ButtonState("y", { if_pressed: ["l", "zr"], enable: true });
43
+ expect(buttonState.isDisabledFlip()).toBe(false);
44
+ expect(buttonState.isAlwaysFlip()).toBe(false);
45
+ expect(buttonState.isFlipIfPressedSelf()).toBe(false);
46
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(true);
47
+ expect(buttonState.hasFlipSetting()).toBe(true);
48
+ expect(buttonState.isRemap()).toBe(false);
49
+ })
50
+ })
51
+ describe('if_pressedに1つの値が入っている時', () => {
52
+ it('isFlipIfPressedSomeButtons()がtrueを返す', () => {
53
+ const buttonState = new ButtonState("y", { if_pressed: ["l"], enable: true });
54
+ expect(buttonState.isDisabledFlip()).toBe(false);
55
+ expect(buttonState.isAlwaysFlip()).toBe(false);
56
+ expect(buttonState.isFlipIfPressedSelf()).toBe(false);
57
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(true);
58
+ expect(buttonState.hasFlipSetting()).toBe(true);
59
+ expect(buttonState.isRemap()).toBe(false);
60
+ })
61
+ })
62
+ describe('if_pressedにbuttonと同じ1つの値が入っている時', () => {
63
+ it('isFlipIfPressedSelf()がtrueを返す', () => {
64
+ const buttonState = new ButtonState("y", { if_pressed: ["y"], enable: true });
65
+ expect(buttonState.isDisabledFlip()).toBe(false);
66
+ expect(buttonState.isAlwaysFlip()).toBe(false);
67
+ expect(buttonState.isFlipIfPressedSelf()).toBe(true);
68
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(false);
69
+ expect(buttonState.hasFlipSetting()).toBe(true);
70
+ expect(buttonState.isRemap()).toBe(false);
71
+ })
72
+ })
73
+ })
74
+
75
+ describe('enableがfalseの時', () => {
76
+ it('isDisabledFlip()がfalseを返す', () => {
77
+ const buttonState = new ButtonState("y", { if_pressed: [], enable: false });
78
+ expect(buttonState.isDisabledFlip()).toBe(true);
79
+ expect(buttonState.isAlwaysFlip()).toBe(false);
80
+ expect(buttonState.isFlipIfPressedSelf()).toBe(false);
81
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(false);
82
+ expect(buttonState.hasFlipSetting()).toBe(false);
83
+ expect(buttonState.isRemap()).toBe(false);
84
+ })
85
+ })
86
+ });
87
+
88
+ describe('remapに値がある時', () => {
89
+ describe("flipに無効な値がある", () => {
90
+ it('isRemap()がtrueを返す', () => {
91
+ const buttonState = new ButtonState("y", { if_pressed: [], enable: false }, { to: ["y"] });
92
+ expect(buttonState.isDisabledFlip()).toBe(true);
93
+ expect(buttonState.isAlwaysFlip()).toBe(false);
94
+ expect(buttonState.isFlipIfPressedSelf()).toBe(false);
95
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(false);
96
+ expect(buttonState.hasFlipSetting()).toBe(false);
97
+ expect(buttonState.isRemap()).toBe(true);
98
+ })
99
+ })
100
+ describe("flipがundefined", () => {
101
+ it('isRemap()がtrueを返す', () => {
102
+ const buttonState = new ButtonState("y", undefined, { to: ["y"] });
103
+ expect(buttonState.isDisabledFlip()).toBe(true);
104
+ expect(buttonState.isAlwaysFlip()).toBe(false);
105
+ expect(buttonState.isFlipIfPressedSelf()).toBe(false);
106
+ expect(buttonState.isFlipIfPressedSomeButtons()).toBe(false);
107
+ expect(buttonState.hasFlipSetting()).toBe(false);
108
+ })
109
+ })
110
+ })
@@ -0,0 +1,52 @@
1
+ import { Button } from "../types/button";
2
+ import { Flip, Macro, Remap } from "../types/buttons_setting_type";
3
+
4
+ export const flip_types = ["disable", "always", "ifPress"] as const;
5
+ export type FlipType = typeof flip_types[number];
6
+
7
+ export class ButtonState {
8
+ button: Button;
9
+ flip?: Flip;
10
+ remap?: Remap;
11
+
12
+ constructor(button: Button, flip?: Flip, remap?: Remap) {
13
+ this.button = button;
14
+ this.flip = flip;
15
+ this.remap = remap;
16
+ };
17
+
18
+ isDisabledFlip(): boolean {
19
+ if(!this.flip && !this.remap) { return true }
20
+ if(this.remap) { return true }
21
+ if(!this.flip) { return false }
22
+ return this.flip && !this.flip?.enable;
23
+ }
24
+
25
+ isAlwaysFlip(): boolean {
26
+ if(!this.flip && !this.remap) { return false }
27
+ if(this.isDisabledFlip()) { return false };
28
+ return !!this.flip && !!this.flip.enable && (this.flip?.if_pressed || []) ?.length === 0;
29
+ }
30
+
31
+ isFlipIfPressedSelf(): boolean {
32
+ if(!this.flip && !this.remap) { return false }
33
+ if(this.isDisabledFlip() || this.isAlwaysFlip() || !this.flip || !this.flip.if_pressed) { return false }
34
+ return this.flip.if_pressed.length === 1 && this.flip.if_pressed[0] === this.button;
35
+ }
36
+
37
+ isFlipIfPressedSomeButtons(): boolean {
38
+ if(!this.flip) { return false }
39
+ if(this.isDisabledFlip() || this.isAlwaysFlip() || this.isFlipIfPressedSelf()) { return false }
40
+ return true;
41
+ }
42
+
43
+ hasFlipSetting(): boolean {
44
+ return !this.isDisabledFlip();
45
+ }
46
+
47
+ isRemap(): boolean {
48
+ if(this.hasFlipSetting()) { return false }
49
+ if(this.remap && this.remap.to?.length > 0) { return true }
50
+ return false;
51
+ }
52
+ }