procon_bypass_man-web 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,25 @@
1
+ module ProconBypassMan
2
+ module Web
3
+ class Storage
4
+ def self.instance
5
+ new
6
+ end
7
+
8
+ def root_path
9
+ ProconBypassMan::Web::Setting.find_or_create_by&.root_path
10
+ end
11
+
12
+ def root_path=(value)
13
+ ProconBypassMan::Web::Setting.find_or_create_by&.update!(root_path: value)
14
+ end
15
+
16
+ def setting_path
17
+ ProconBypassMan::Web::Setting.find_or_create_by&.setting_path
18
+ end
19
+
20
+ def setting_path=(value)
21
+ ProconBypassMan::Web::Setting.find_or_create_by&.update!(setting_path: value)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProconBypassMan
4
+ module Web
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
data/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "procon_bypass_man-web",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "license": "MIT",
6
+ "devDependencies": {
7
+ "@babel/core": "^7.14.8",
8
+ "@babel/preset-env": "^7.14.9",
9
+ "@babel/preset-react": "^7.14.5",
10
+ "@babel/preset-typescript": "^7.14.5",
11
+ "@emotion/react": "^11.4.0",
12
+ "@types/jest": "^26.0.24",
13
+ "@types/js-yaml": "^4.0.2",
14
+ "@types/lodash": "^4.14.172",
15
+ "@types/md5": "^2.3.1",
16
+ "@types/react": "^17.0.11",
17
+ "@types/react-dom": "^17.0.8",
18
+ "babel-jest": "^27.0.6",
19
+ "babel-loader": "^8.2.2",
20
+ "deep-object-diff": "^1.1.0",
21
+ "file-loader": "^6.2.0",
22
+ "html-webpack-plugin": "^5.3.1",
23
+ "jest": "^27.0.6",
24
+ "js-yaml": "^4.1.0",
25
+ "lodash": "^4.17.21",
26
+ "md5": "^2.3.0",
27
+ "ts-jest": "^27.0.4",
28
+ "ts-loader": "^9.2.3",
29
+ "ts-node": "^10.1.0",
30
+ "typescript": "^4.3.4",
31
+ "webpack": "^5.39.1",
32
+ "webpack-cli": "^4.7.2",
33
+ "webpack-dev-server": "^3.11.2"
34
+ },
35
+ "scripts": {
36
+ "build": "webpack",
37
+ "server": "webpack serve",
38
+ "release-build": "NODE_ENV=production webpack",
39
+ "test": "jest"
40
+ },
41
+ "dependencies": {
42
+ "@types/react-router-dom": "^5.1.7",
43
+ "axios": "^0.21.1",
44
+ "react": "^17.0.2",
45
+ "react-dom": "^17.0.2",
46
+ "react-router-dom": "^5.2.0"
47
+ }
48
+ }
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/procon_bypass_man/web/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "procon_bypass_man-web"
7
+ spec.version = ProconBypassMan::Web::VERSION
8
+ spec.authors = ["jiikko"]
9
+ spec.email = ["n905i.1214@gmail.com"]
10
+
11
+ spec.summary = "PBM for web"
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/splaplapla/procon_bypass_man-web"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ # Uncomment to register a new dependency of your gem
30
+ spec.add_dependency "sinatra"
31
+ spec.add_dependency "webrick"
32
+ spec.add_dependency "sqlite3"
33
+
34
+ # For more information and examples about making a new gem, checkout our
35
+ # guide at: https://bundler.io/guides/creating_gem.html
36
+ end
data/src/app.tsx ADDED
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom";
3
+ import { Top } from "./pages/top";
4
+
5
+ ReactDOM.render(<Top />, document.getElementById("app"));
@@ -0,0 +1,142 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useContext } from "react";
5
+ import { Button } from "../types/button";
6
+ import { ButtonState } from "./../lib/button_state";
7
+ import { ButtonsModal } from "./buttons_modal";
8
+ import { ButtonsSettingContext } from "./../contexts/buttons_setting";
9
+ import { ButtonsSettingType, ButtonsInLayer, ButtonInLayer, Layers, Flip } from "../types/buttons_setting_type";
10
+ import { LayerKey } from "../types/layer_key";
11
+ import { disableFlipType, alwaysFlipType, flipIfPressedSelfType, flipIfPressedSomeButtonsType, ignoreButtonsInFlipingType, remapType, openMenuType, closeMenuType } from "../reducers/layer_reducer";
12
+
13
+ type ButtonMenuProp = {
14
+ name: Button;
15
+ layerKey: LayerKey;
16
+ buttonValue: ButtonInLayer;
17
+ layersDispatch: any;
18
+ };
19
+
20
+ const ButtonMenu = ({ name, layerKey, buttonValue, layersDispatch }: ButtonMenuProp) => {
21
+ const flipRadioName = `${layerKey}_button_menu_${name}`;
22
+ const buttonState = new ButtonState(name, buttonValue.flip, buttonValue.remap);
23
+
24
+ // for modal
25
+ const [openModal, setOpenModal] = useState(false)
26
+ const [modalCallbackOnSubmit, setModalCallbackOnSubmit] = useState(undefined as any)
27
+ const [modalCloseCallback, setModalCloseCallback] = useState(undefined as any)
28
+ const [modalTitle, setModalTitle] = useState("")
29
+ const [modalPrefillButtons, setModalPrefillButtons] = useState<Array<Button>>([])
30
+
31
+ // 無効
32
+ const handleNullFlipValue = (e: React.ChangeEvent<HTMLInputElement>) => {
33
+ layersDispatch({ type: disableFlipType, payload: { layerKey: layerKey, button: name }});
34
+ };
35
+
36
+ // 常に連打
37
+ const handleFlipValue = (e: React.ChangeEvent<HTMLInputElement>) => {
38
+ layersDispatch({ type: alwaysFlipType, payload: { layerKey: layerKey, button: name }});
39
+ };
40
+
41
+ // 自分自身への条件付き連打
42
+ const openIfPressedRadioboxModal = (e: React.ChangeEvent<HTMLInputElement>) => {
43
+ layersDispatch({ type: flipIfPressedSelfType, payload: { layerKey: layerKey, button: name }});
44
+ };
45
+
46
+ // 条件付き連打
47
+ const flipIfPressedSomeButtons = buttonValue?.flip?.if_pressed || [] as Array<Button>;
48
+ const setFlipIfPressedSomeButtonsWithPersistence = (bs: Array<Button>) => {
49
+ layersDispatch({ type: flipIfPressedSomeButtonsType, payload: { layerKey: layerKey, button: name, targetButtons: bs }});
50
+ }
51
+ const openIfPressedSomeButtonsModal = (e: React.ChangeEvent<HTMLInputElement> | React.MouseEvent<HTMLInputElement>) => {
52
+ setOpenModal(true)
53
+ setModalTitle("特定のキーを押したときだけ")
54
+ setModalPrefillButtons(flipIfPressedSomeButtons);
55
+ setModalCallbackOnSubmit(() => setFlipIfPressedSomeButtonsWithPersistence);
56
+ setModalCloseCallback(() => setOpenModal);
57
+ }
58
+
59
+ // 無視
60
+ const forceNeutralButtons = buttonValue.flip?.force_neutral || [] as Array<Button>
61
+ const setIgnoreButtonsOnFlipingWithPersistence = (bs: Array<Button>) => {
62
+ layersDispatch({ type: ignoreButtonsInFlipingType, payload: { layerKey: layerKey, button: name, targetButtons: bs }});
63
+ }
64
+ const handleIgnoreButton = (e: React.ChangeEvent<HTMLInputElement>) => {
65
+ setOpenModal(true)
66
+ setModalTitle("連打中は特定のボタンの入力を無視する")
67
+ setModalPrefillButtons(buttonValue.flip?.force_neutral || [] as Array<Button>);
68
+ setModalCallbackOnSubmit(() => setIgnoreButtonsOnFlipingWithPersistence);
69
+ setModalCloseCallback(() => setOpenModal);
70
+ };
71
+
72
+ // リマップ
73
+ const setRemapButtonsWithPersistence = (bs: Array<Button>) => {
74
+ layersDispatch({ type: remapType, payload: { layerKey: layerKey, button: name, targetButtons: bs }});
75
+ }
76
+ const handleRemapButton = (e: React.ChangeEvent<HTMLInputElement>) => {
77
+ setOpenModal(true)
78
+ setModalTitle("リマップ")
79
+ setModalPrefillButtons(buttonValue.remap?.to || []);
80
+ setModalCallbackOnSubmit(() => setRemapButtonsWithPersistence);
81
+ setModalCloseCallback(() => setOpenModal);
82
+ };
83
+
84
+ return(
85
+ <>
86
+ <fieldset><legend><strong>連打設定</strong></legend>
87
+ <label><input type="radio" onChange={handleNullFlipValue} checked={buttonState.isDisabledFlip()}/>無効</label><br />
88
+ <label><input type="radio" onChange={handleFlipValue} checked={buttonState.isAlwaysFlip()}/>常に連打する</label><br />
89
+ <label><input type="radio" onChange={openIfPressedRadioboxModal} checked={buttonState.isFlipIfPressedSelf()}/>このボタンを押している時だけ連打する({name})</label><br />
90
+ <label>
91
+ <input type="radio" onChange={openIfPressedSomeButtonsModal} onClick={openIfPressedSomeButtonsModal} checked={buttonState.isFlipIfPressedSomeButtons()}/>
92
+ 特定のキーを押したときだけ連打する{flipIfPressedSomeButtons.length > 0 && `(${flipIfPressedSomeButtons.join(", ")})`}
93
+ </label>
94
+ </fieldset>
95
+
96
+ <fieldset><legend><strong>連打オプション</strong></legend>
97
+ <label>
98
+ <input type="checkbox" onChange={handleIgnoreButton} checked={forceNeutralButtons.length > 0} disabled={buttonState.isDisabledFlip()} />
99
+ 連打中は特定のボタンの入力を無視する{forceNeutralButtons.length > 0 && `(${forceNeutralButtons.join(", ")})`}
100
+ </label>
101
+ </fieldset>
102
+
103
+ <fieldset><legend><strong>リマップ設定</strong></legend>
104
+ <label>
105
+ <input type="checkbox" onChange={handleRemapButton} checked={buttonState.isRemap()} disabled={!buttonState.isDisabledFlip()} />
106
+ 別のボタンに置き換える{buttonState.isRemap() && `(${buttonValue.remap?.to?.join(", ")})`}
107
+ </label>
108
+ </fieldset>
109
+ <div css={css`position: relative;`}>
110
+ {openModal && <ButtonsModal callbackOnSubmit={modalCallbackOnSubmit} callbackOnClose={modalCloseCallback} title={modalTitle} prefill={modalPrefillButtons} positionOnShown={"relative"} />}
111
+ </div>
112
+ </>
113
+ )
114
+ }
115
+
116
+ type Prop = {
117
+ name: Button;
118
+ layerKey: LayerKey;
119
+ };
120
+
121
+ export const ButtonSetting: React.FC<Prop> = ({ name, layerKey }) => {
122
+ const settingContext = useContext(ButtonsSettingContext);
123
+ const handleToggle = () => {
124
+ if(isOpenMenu()) { // 閉じる
125
+ settingContext.layersDispatch({ type: closeMenuType, payload: { layerKey: layerKey, button: name }});
126
+ } else { // 開く
127
+ settingContext.layersDispatch({ type: openMenuType, payload: { layerKey: layerKey, button: name }});
128
+ }
129
+ }
130
+
131
+ const isOpenMenu = () => {
132
+ return settingContext.layers[layerKey][name].open;
133
+ }
134
+ const buttonValue = settingContext.layers[layerKey][name] || {} as ButtonInLayer;
135
+
136
+ return (
137
+ <>
138
+ <label><input type="checkbox" checked={isOpenMenu()} onChange={handleToggle}/>{name}</label>
139
+ {isOpenMenu() && <ButtonMenu name={name} layerKey={layerKey} buttonValue={buttonValue} layersDispatch={settingContext.layersDispatch} />}
140
+ </>
141
+ );
142
+ };
@@ -0,0 +1,110 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState } from "react";
5
+ import { Button, buttons } from "../types/button";
6
+
7
+ type Props = {
8
+ callbackOnSubmit: any;
9
+ callbackOnClose: any;
10
+ prefill: Array<Button>;
11
+ title: string;
12
+ positionOnShown: string;
13
+ };
14
+
15
+ type CheckedButtons = {
16
+ [key in Button] : boolean
17
+ }
18
+
19
+ export const ButtonsModal = ({ callbackOnSubmit, callbackOnClose, title, prefill, positionOnShown }: Props) => {
20
+ const [checkedButtonMap, setCheckedButtonMap] = useState(
21
+ prefill.reduce((a, b) => { a[b] = true; return a },
22
+ buttons.reduce((a, b) => { a[b] = false; return a }, {} as CheckedButtons)
23
+ )
24
+ )
25
+ const callback = callbackOnSubmit;
26
+ const handleSubmit = (e: React.MouseEvent<HTMLAnchorElement>) => {
27
+ e.preventDefault();
28
+ const bs = Object.entries(checkedButtonMap).reduce((acc, item) => {
29
+ const checked: boolean = item[1];
30
+ const button = item[0] as Button;
31
+ checked && acc.push(button);
32
+ return acc;
33
+ }, [] as Array<Button>).sort();
34
+
35
+ callbackOnSubmit(bs);
36
+ callbackOnClose(false);
37
+ };
38
+ const handleCancel = (e: React.MouseEvent<HTMLAnchorElement>) => {
39
+ e.preventDefault();
40
+ callbackOnClose(false);
41
+ }
42
+ const titlestyle = css(`
43
+ margin-top: 10px;
44
+ font-size: 1.17em;
45
+ font-weight: bold;
46
+ `)
47
+ const style = () => {
48
+ if(positionOnShown === "relative") {
49
+ return css(`
50
+ position: absolute;
51
+ align: left;
52
+ top: -400px;
53
+ width: 400px;
54
+ height: 400px;
55
+ border: solid;
56
+ background-color: white;
57
+ `);
58
+ } else {
59
+ return css(`
60
+ position: absolute;
61
+ align: left;
62
+ top: 0px;
63
+ left: 20px;
64
+ width: 400px;
65
+ height: 400px;
66
+ border: solid;
67
+ background-color: white;
68
+ `);
69
+ }
70
+ }
71
+ const aStyle = css`
72
+ background-color: #4669ff;
73
+ border-bottom: solid 2px #003aff;
74
+ border-right: solid 2px #003aff;
75
+ border-radius: 20px;
76
+ font-weight: bold;
77
+ color: #FFF;
78
+ text-decoration: none;
79
+ padding: 10px;
80
+ display: inline-block;
81
+ margin-left: 10px;
82
+ `;
83
+
84
+ const handleClick = (e: React.ChangeEvent<HTMLInputElement>) => {
85
+ setCheckedButtonMap((previousButtonStats) => {
86
+ previousButtonStats[e.target.value as Button] = e.target.checked;
87
+ return previousButtonStats;
88
+ })
89
+ }
90
+
91
+ return (
92
+ <>
93
+ <div css={style()}>
94
+ <div css={titlestyle}>{title}</div>
95
+
96
+ {buttons.map((b, index) => (
97
+ <div key={index}>
98
+ <label><input type="checkbox" value={b} defaultChecked={checkedButtonMap[b]} onChange={handleClick} />{b}</label>
99
+ </div>
100
+ ))}
101
+
102
+ <hr />
103
+ <div css={css`display: flex`}>
104
+ <a href={"#"} onClick={handleCancel} css={aStyle}>変更せず閉じる</a>
105
+ <a href={"#"} onClick={handleSubmit} css={aStyle}>決定する</a>
106
+ </div>
107
+ </div>
108
+ </>
109
+ )
110
+ }
@@ -0,0 +1,67 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useContext } from "react";
5
+ import { ButtonSetting } from "./button_setting";
6
+ import { MacroSettings } from "./macro_settings";
7
+ import { ModeSettings } from "./mode_settings";
8
+ import { Button, buttons } from "../types/button";
9
+ import { LayerKey } from "../types/layer_key";
10
+ import { ButtonsSettingContext } from "./../contexts/buttons_setting";
11
+
12
+ type Props = {
13
+ layerKey: LayerKey;
14
+ layerRef: any;
15
+ };
16
+
17
+ export const ButtonsSetting = ({ layerKey, layerRef }:Props) => {
18
+ const [visibility, setVisibility] = useState("hidden");
19
+ const visibilityStyle = () => {
20
+ if(visibility === "hidden") {
21
+ return css`display: none;`;
22
+ }
23
+ }
24
+ const ulStyle = css`
25
+ border: 1px solid #666;
26
+ display: flex;
27
+ flex-wrap: wrap;
28
+ justify-content: center;
29
+ list-style-type: none;
30
+ margin: 0 0 0 1em;
31
+ padding: 0;
32
+ width: 900px;
33
+ `;
34
+ const liStyle = css`
35
+ border: 1px solid #aaa;
36
+ margin: 0.2em;
37
+ padding: 0.5em;
38
+ width: 200px;
39
+ `;
40
+ layerRef.setVisibility = setVisibility;
41
+
42
+ const { layers } = useContext(ButtonsSettingContext);
43
+ const isEnableMode = !layers[layerKey].mode.disable;
44
+
45
+ return(
46
+ <div css={visibilityStyle()}>
47
+ <h4>モード</h4>
48
+ <ModeSettings layerKey={layerKey} />
49
+
50
+ <h4>マクロ</h4>
51
+ {isEnableMode && `モードが有効なので選択できません`}
52
+ {!isEnableMode && <MacroSettings layerKey={layerKey} />}
53
+
54
+ <h4>各ボタンの設定</h4>
55
+ {isEnableMode && `モードが有効なので選択できません`}
56
+ {!isEnableMode &&
57
+ <div css={ulStyle}>
58
+ {buttons.map((b, i) => (
59
+ <div key={i} css={liStyle}>
60
+ <ButtonSetting layerKey={layerKey} name={b} />
61
+ </div>
62
+ ))}
63
+ </div>
64
+ }
65
+ </div>
66
+ )
67
+ }