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,93 @@
1
+ import axios from 'axios';
2
+ import { PbmStats } from "../types/pbm_stats";
3
+ import { Button } from "../types/button";
4
+ import { LayerKey } from "../types/layer_key";
5
+ import { ButtonsSettingType } from "../types/buttons_setting_type";
6
+
7
+ interface DirPathApiResponse {
8
+ result: string,
9
+ root_path: string,
10
+ }
11
+
12
+ interface SettingPathApiResponse {
13
+ result: string,
14
+ setting_path: string,
15
+ }
16
+
17
+ interface PostApiResponse {
18
+ result: string,
19
+ }
20
+
21
+ interface StatsApiResponse {
22
+ stats: PbmStats,
23
+ result: string,
24
+ pid: number | null,
25
+ }
26
+
27
+ export interface SettingApiResponse {
28
+ result: string,
29
+ setting: ButtonsSettingType,
30
+ setting_group_by_button: any,
31
+ installed_macros: Array<string>,
32
+ installed_modes: Array<string>,
33
+ }
34
+
35
+ interface SettingDigestApiResponse {
36
+ result: string,
37
+ digest: string,
38
+ }
39
+
40
+ export class HttpClient {
41
+ constructor() {
42
+ };
43
+
44
+ getDirPath() {
45
+ const path = "/api/pbm_root__path";
46
+ return axios.get<DirPathApiResponse>(`/api/pbm_root_path`);
47
+ }
48
+
49
+ postDirPath(dirPath: string) {
50
+ return axios.post<PostApiResponse>(`/api/pbm_root_path`, { root_path: dirPath });
51
+ }
52
+
53
+ getSettingPath() {
54
+ return axios.get<SettingPathApiResponse>( "/api/pbm_setting_path");
55
+ }
56
+
57
+ postSettingPath(settingPath: string) {
58
+ const path = "/api/pbm_setting_path"
59
+ return axios.post<PostApiResponse>(`${path}`, { setting_path: settingPath });
60
+ }
61
+
62
+ getPbmStats() {
63
+ const path = "/api/pbm_stats";
64
+ return axios.get<StatsApiResponse>(`${path}`);
65
+ }
66
+
67
+ startPbm() {
68
+ const path = "/api/start_pbm";
69
+ return axios.post<PostApiResponse>(`${path}`);
70
+ }
71
+
72
+ stopPbm() {
73
+ const path = "/api/stop_pbm";
74
+ return axios.post<PostApiResponse>(`${path}`);
75
+ }
76
+
77
+ reloadPbmSetting() {
78
+ const path = "/api/reload_pbm_setting";
79
+ return axios.post<PostApiResponse>(`${path}`);
80
+ }
81
+
82
+ getSetting() {
83
+ return axios.get<SettingApiResponse>("/api/pbm_setting");
84
+ }
85
+
86
+ postSetting(settingYaml: string) {
87
+ return axios.post<PostApiResponse>("/api/pbm_setting", { setting_yaml: settingYaml });
88
+ }
89
+
90
+ getSettingDigest() {
91
+ return axios.get<SettingDigestApiResponse>("/api/pbm_setting_digest");
92
+ }
93
+ }
@@ -0,0 +1,92 @@
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
+ import { PbmStats } from "../types/pbm_stats";
7
+
8
+ type Prop = {
9
+ };
10
+
11
+ const httpClient = new HttpClient();
12
+
13
+ export const BpmPage= ({}:Prop) => {
14
+ const [pbmStats, setPbmStats] = useState("");
15
+
16
+ const handlePbmStats = (e: React.MouseEvent<HTMLElement>) => {
17
+ httpClient.getPbmStats()
18
+ .then(function (response) {
19
+ setPbmStats(`${response.data.stats}(${response.data.pid})`);
20
+ })
21
+ }
22
+ const handleStartPbm = (e: React.MouseEvent<HTMLElement>) => {
23
+ httpClient.startPbm()
24
+ .then(function (response) {
25
+ if(response.data.result === "ok") {
26
+ setPbmStats("waiting" as PbmStats);
27
+ } else {
28
+ setPbmStats("error" as PbmStats);
29
+ }
30
+ })
31
+ .catch(function (error) {
32
+ console.log(error);
33
+ })
34
+ }
35
+
36
+ const handleStopPbm = (e: React.MouseEvent<HTMLElement>) => {
37
+ httpClient.stopPbm()
38
+ .then(function (response) {
39
+ if(response.data.result === "ok") {
40
+ setPbmStats("waiting" as PbmStats);
41
+ } else {
42
+ setPbmStats("error" as PbmStats);
43
+ }
44
+ })
45
+ .catch(function (error) {
46
+ console.log(error);
47
+ })
48
+ }
49
+
50
+ const handleReloadPbmSetting = (e: React.MouseEvent<HTMLElement>) => {
51
+ httpClient.reloadPbmSetting()
52
+ .then(function (response) {
53
+ if(response.data.result === "ok") {
54
+ setPbmStats("stopped" as PbmStats);
55
+ } else {
56
+ setPbmStats("error" as PbmStats);
57
+ }
58
+ })
59
+ .catch(function (error) {
60
+ console.log(error);
61
+ })
62
+ }
63
+
64
+ const isShowRestartButton = () => {
65
+ return pbmStats == "running";
66
+ }
67
+
68
+ const isShowStartButton = () => {
69
+ return pbmStats == "stopped";
70
+ }
71
+
72
+ const isShowStopButton = () => {
73
+ return pbmStats == "running";
74
+ }
75
+
76
+ useEffect(() => {
77
+ httpClient.getPbmStats()
78
+ .then(function (response) {
79
+ setPbmStats(`${response.data.stats}(${response.data.pid})`);
80
+ })
81
+ }, [])
82
+
83
+ return (
84
+ <>
85
+ <h2>PBMのステータス: {pbmStats}</h2>
86
+ <input type="button" onClick={handlePbmStats} value="現在のステータスを取得する" />
87
+ {isShowStopButton() && <input type="button" onClick={handleStopPbm} value="停止する" />}
88
+ {isShowStartButton() && <input type="button" onClick={handleStartPbm} value="開始する" />}
89
+ {pbmStats === "running" && <input type="button" onClick={handleReloadPbmSetting} value="設定を再読み込みする" />}
90
+ </>
91
+ )
92
+ }
@@ -0,0 +1,281 @@
1
+ /** @jsx jsx */
2
+
3
+ import { jsx, css } from '@emotion/react'
4
+ import React, { useState, useEffect, useContext, useRef } from "react";
5
+ import { ButtonsSetting } from "../components/buttons_setting";
6
+ import { Button, buttons } from "../types/button";
7
+ import { LayerKey, layerKeys } from "../types/layer_key";
8
+ import { ButtonInLayer, ButtonsInLayer, ButtonsSettingType, Layers, Flip, Macro, StructMacro, ModeTable, StructMode } from "../types/buttons_setting_type";
9
+ import { HttpClient, SettingApiResponse } from "../lib/http_client";
10
+ import { ButtonState } from "./../lib/button_state";
11
+ import { ButtonStateDiff } from "./../lib/button_state_diff";
12
+ import { ButtonsSettingContext, } from "./../contexts/buttons_setting";
13
+ import { ButtonsSettingConverter } from "./../lib/buttons_setting_converter";
14
+ import { disableFlipType, alwaysFlipType, flipIfPressedSelfType, flipIfPressedSomeButtonsType, ignoreButtonsInFlipingType, remapType, closeMenuType, applyMacroType, installMacroType, installModeType, applyModeType } from "../reducers/layer_reducer";
15
+ import { ButtonsModal } from "../components/buttons_modal";
16
+ import { InstallableMacros } from "../components/installable_macros";
17
+ import { InstallableModes } from "../components/installable_modes";
18
+ import _ from 'lodash';
19
+ import md5 from 'md5';
20
+
21
+ const httpClient = new HttpClient();
22
+
23
+ interface LayerRef {
24
+ setVisibility(status: string): string;
25
+ };
26
+
27
+ export const ButtonsSettingPage = () => {
28
+ const { loaded, setLoaded, layers, layersDispatch, prefixKeys, setPrefixKeys } = useContext(ButtonsSettingContext);
29
+ const [selectedLayer, setSelectedLayer] = useState<LayerKey>("up");
30
+ const layerRefs = layerKeys.map((l) => ({} as LayerRef));
31
+ const [initializedSetting, setInitializedSetting] = useState({} as ButtonsSettingType)
32
+ const [infoMessage, setInfoMessage] = useState(undefined as undefined | string)
33
+
34
+ const switchLayer = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
35
+ if (event.target instanceof HTMLElement) {
36
+ setSelectedLayer(event.target.dataset.layerKey as LayerKey);
37
+ layerRefs.forEach(r => r.setVisibility("hidden"));
38
+ layerRefs[
39
+ Number(event.target.dataset.layerKeyIndex)
40
+ ].setVisibility("shown");
41
+ }
42
+ }
43
+ const exportSetting = () => {
44
+ const body = ButtonsSettingConverter({ prefixKeys: prefixKeys, layers: layers })
45
+ var data = new Blob([body], { type: 'text/yaml' })
46
+ var csvURL = window.URL.createObjectURL(data);
47
+ const tempLink = document.createElement('a');
48
+ tempLink.href = csvURL;
49
+ tempLink.setAttribute('download', 'setting.yml');
50
+ tempLink.click();
51
+ }
52
+ const applySetting = (e: React.MouseEvent<HTMLAnchorElement>) => {
53
+ e.preventDefault();
54
+ httpClient.postSetting(
55
+ ButtonsSettingConverter({ prefixKeys: prefixKeys, layers: layers })
56
+ ).then(
57
+ function (response) {
58
+ alert("設定ファイルのパスへ書き込みが完了しました");
59
+ reset();
60
+ });
61
+ }
62
+ const changes = (): Array<string> => {
63
+ return ButtonStateDiff({
64
+ before: initializedSetting,
65
+ after: { prefix_keys_for_changing_layer: prefixKeys, layers: layers },
66
+ })
67
+ }
68
+ const reset = () => {
69
+ setInitializedSetting({
70
+ prefix_keys_for_changing_layer: prefixKeys,
71
+ layers: _.cloneDeep(layers),
72
+ });
73
+ isUptodate()
74
+ }
75
+ const isUptodate = () => {
76
+ httpClient.getSettingDigest().then(function (response) {
77
+ const digest = md5(
78
+ ButtonsSettingConverter({ prefixKeys: prefixKeys, layers: layers })
79
+ )
80
+ console.log("digest:", digest);
81
+ if(digest === response.data.digest) {
82
+ console.log("最新です")
83
+ setInfoMessage("設定が反映済みです。")
84
+ } else {
85
+ setInfoMessage("設定が未反映です. PBMの再起動が必要です。")
86
+ }
87
+ }).catch(function (error) {
88
+ if (error.response.status === 404) {
89
+ setInfoMessage("PBMを起動していません。")
90
+ } else {
91
+ console.log("想定外のエラーです");
92
+ }
93
+ })
94
+ }
95
+
96
+ useEffect(() => {
97
+ if (loaded) {
98
+ reset();
99
+
100
+ layerRefs[0].setVisibility("shown");
101
+ return;
102
+ }
103
+
104
+ httpClient.getSetting()
105
+ .then(function (response) {
106
+ const body = response.data.setting_group_by_button as SettingApiResponse;
107
+
108
+ if(body.installed_macros) {
109
+ body.installed_macros.forEach((installed_macro: string) => {
110
+ layersDispatch({ type: installMacroType, payload: { installed_macro: installed_macro }});
111
+ })
112
+ }
113
+
114
+ if(body.installed_modes) {
115
+ body.installed_modes.forEach((modeName: string) => {
116
+ layersDispatch({ type: installModeType, payload: { installed_mode: modeName } });
117
+ })
118
+ }
119
+
120
+ setPrefixKeys(response.data.setting.prefix_keys_for_changing_layer);
121
+ const layers = layerKeys.reduce((a, key) => {
122
+ a[key] = response.data.setting_group_by_button.layers[key];
123
+ return a;
124
+ }, {} as Layers)
125
+
126
+ layerKeys.forEach((layerKey) => {
127
+ const macroTable = layers[layerKey].macro as Macro
128
+ if(macroTable) {
129
+ Object.entries(macroTable).forEach((val, i) => {
130
+ const name = val[0];
131
+ const ifPressed = val[1];
132
+ const macro = { name: name, if_pressed: ifPressed } as StructMacro
133
+ layersDispatch({ type: applyMacroType, payload: { layerKey: layerKey, macro: macro }});
134
+ })
135
+ }
136
+ const modeTable = layers[layerKey].mode as ModeTable;
137
+ if(modeTable) {
138
+ Object.entries(modeTable).forEach((val, i) => {
139
+ const name = val[0];
140
+ const mode = { name: name } as StructMode;
141
+ layersDispatch({ type: applyModeType, payload: { layerKey: layerKey, mode: mode }});
142
+ })
143
+ } else {
144
+ layersDispatch({ type: applyModeType, payload: { layerKey: layerKey, mode: { name: "disable" } }});
145
+ }
146
+
147
+ buttons.forEach((button) => {
148
+ const buttonState = new ButtonState(
149
+ button,
150
+ layers[layerKey][button]?.flip, layers[layerKey][button]?.remap,
151
+ );
152
+ const flip = layers[layerKey][button]?.flip || {} as Flip
153
+
154
+ if(layers[layerKey][button] === undefined) {
155
+ layersDispatch({ type: closeMenuType, payload: { layerKey: layerKey, button: button }});
156
+ } else if (layers[layerKey][button].remap?.to) {
157
+ layersDispatch({ type: remapType, payload: { layerKey: layerKey, button: button, targetButtons: layers[layerKey][button].remap?.to }});
158
+ } else if (buttonState.hasFlipSetting()) {
159
+ layersDispatch({ type: ignoreButtonsInFlipingType, payload: { layerKey: layerKey, button: button, targetButtons: flip.force_neutral }});
160
+
161
+ if(buttonState.isDisabledFlip()) {
162
+ layersDispatch({ type: disableFlipType, payload: { layerKey: layerKey, button: button }});
163
+ } else if (buttonState.isAlwaysFlip()) {
164
+ layersDispatch({ type: alwaysFlipType, payload: { layerKey: layerKey, button: button }});
165
+ } else if (buttonState.isFlipIfPressedSelf()) {
166
+ layersDispatch({ type: flipIfPressedSelfType, payload: { layerKey: layerKey, button: button }});
167
+ } else if (buttonState.isFlipIfPressedSomeButtons()) {
168
+ layersDispatch({ type: flipIfPressedSomeButtonsType, payload: { layerKey: layerKey, button: button, targetButtons: layers[layerKey][button].flip?.if_pressed }});
169
+ }
170
+ } else {
171
+ console.log("unexpectですです!!!!!!!!!!!!!");
172
+ }
173
+ });
174
+ });
175
+
176
+ setLoaded(true);
177
+ }).catch(function (error) {
178
+ if (error.response.status === 404) {
179
+ setInfoMessage("設定ファイルのパスが未設定です")
180
+ } else {
181
+ console.log("想定外のエラーです");
182
+ }
183
+ })
184
+
185
+ }, [loaded]);
186
+
187
+ const layersTabStyle = () => {
188
+ return css`
189
+ list-style: none;
190
+ display:flex;
191
+ margin: 0;
192
+ padding: 0;
193
+ border-left: 1px solid #aaa;
194
+ margin-bottom: 30px;
195
+ li {
196
+ border-top: 1px solid #aaa;
197
+ border-right: 1px solid #aaa;
198
+ &.active {
199
+ border-bottom: 1px solid #white;
200
+ }
201
+ &.inactive {
202
+ border-bottom: 1px solid #aaa;
203
+ }
204
+ a {
205
+ padding: 20px;
206
+ display: block;
207
+ &:hover {
208
+ cursor:pointer;
209
+ }
210
+ }
211
+ }
212
+ `;
213
+ }
214
+ const liClassName = (layer: LayerKey) => {
215
+ if(layer === selectedLayer) {
216
+ return "active"
217
+ } else {
218
+ return "inactive"
219
+ };
220
+ }
221
+ const handlePrefixKeysField = () => {
222
+ setOpenModal(true)
223
+ setModalTitle("キープレフィックスの変更")
224
+ setModalPrefillButtons(prefixKeys);
225
+ setModalCallbackOnSubmit(() => setPrefixKeys);
226
+ setModalCloseCallback(() => setOpenModal);
227
+ }
228
+
229
+ // for modal
230
+ const [openModal, setOpenModal] = useState(false)
231
+ const [modalCallbackOnSubmit, setModalCallbackOnSubmit] = useState(undefined as any)
232
+ const [modalCloseCallback, setModalCloseCallback] = useState(undefined as any)
233
+ const [modalTitle, setModalTitle] = useState("")
234
+ const [modalPrefillButtons, setModalPrefillButtons] = useState<Array<Button>>([])
235
+
236
+ return(
237
+ <>
238
+ <div css={css`display: table`}>
239
+ <div css={css`display: table-cell; width: 400px;`}>
240
+ <h2>設定ファイルの変更</h2>
241
+ <div>
242
+ <a href="#" onClick={exportSetting}>エクスポートする</a>
243
+ </div>
244
+
245
+ <h3>インストール可能なマクロ</h3>
246
+ {loaded && <InstallableMacros />}
247
+
248
+ <h3>インストール可能なモード</h3>
249
+ {loaded && <InstallableModes />}
250
+
251
+ <h3>設定中のプレフィックスキー</h3>
252
+ <div css={css`position: relative; margin-bottom: 20px;`}>
253
+ <input type="text" value={prefixKeys.join(", ")} readOnly={true} onClick={handlePrefixKeysField} />
254
+ {openModal && <ButtonsModal callbackOnSubmit={modalCallbackOnSubmit} callbackOnClose={modalCloseCallback} title={modalTitle} prefill={modalPrefillButtons} positionOnShown={"stay"} />}
255
+ </div>
256
+ </div>
257
+ <div css={css`display: table-cell`}>
258
+ <h2></h2>
259
+ <div>
260
+ <a href="#" onClick={applySetting}>変更した設定でsetting.ymlへ上書きする</a>
261
+ <div>{infoMessage}</div>
262
+ <ul>
263
+ {loaded && changes().map((c, i) => <li key={i}>{c}</li>)}
264
+ </ul>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ <ul css={layersTabStyle()}>
270
+ {layerKeys.map((l, index) => (
271
+ <li key={l} className={`${liClassName(l)}`}>
272
+ <a data-layer-key-index={index} data-layer-key={l} onClick={switchLayer}>{l}</a>
273
+ </li>
274
+ ))}
275
+ </ul>
276
+
277
+ {loaded && layerKeys.map((l, index) => (<ButtonsSetting key={index} layerKey={l} layerRef={layerRefs[index]} />))}
278
+ {!loaded && "loading..."}
279
+ </>
280
+ )
281
+ }