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,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
+ }