Browse Source

implemented everything I did in support/hotkey repository

Makoto Shiraishi 5 years ago
parent
commit
293f0bd2e4

+ 1 - 0
resource/locales/en_US/translation.json

@@ -319,6 +319,7 @@
       "Edit Page": "Edit Page",
       "Create Page": "Create Page",
       "Show Contributors": "Show Contributors",
+      "MirrorMode": "Mirror Mode",
       "Konami Code": "Konami Code",
       "konami_code_url": "https://en.wikipedia.org/wiki/Konami_Code"
     },

+ 1 - 0
resource/locales/ja_JP/translation.json

@@ -320,6 +320,7 @@
       "Edit Page": "ページ編集",
       "Create Page": "ページ作成",
       "Show Contributors": "コントリビューター<br>を表示",
+      "MirrorMode": "ミラーモード",
       "Konami Code": "コナミコマンド",
       "konami_code_url": "https://ja.wikipedia.org/wiki/コナミコマンド"
     },

+ 3 - 2
src/client/js/base.jsx

@@ -7,7 +7,7 @@ import SearchTop from './components/Navbar/SearchTop';
 import GrowiNavbar from './components/Navbar/GrowiNavbar';
 import NavbarToggler from './components/Navbar/NavbarToggler';
 import Sidebar from './components/Sidebar';
-import StaffCredit from './components/StaffCredit/StaffCredit';
+import Hotkeys from './components/Hotkeys/Hotkeys';
 
 import AppContainer from './services/AppContainer';
 import WebsocketContainer from './services/WebsocketContainer';
@@ -49,7 +49,8 @@ const componentMappings = {
 
   'grw-sidebar-wrapper': <Sidebar />,
 
-  'staff-credit': <StaffCredit />,
+  hotkeys: <Hotkeys />,
+
 };
 
 export { appContainer, componentMappings };

+ 73 - 0
src/client/js/components/Hotkeys/Hotkeys.jsx

@@ -0,0 +1,73 @@
+import React from 'react';
+import HotkeysDetector from '../HotkeysDetector/HotkeysDetector';
+import StaffCredit from '../StaffCredit/StaffCredit';
+import MirrorMode from '../MirrorMode/MirrorMode';
+
+export default class Hotkeys extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      view: [],
+    };
+    this.supportClasses = [
+      StaffCredit,
+      MirrorMode,
+    ];
+    this.keymap = this.keymapSet();
+    this.deleteRender = this.deleteRender.bind(this);
+    this.hotkeyList = this.hotkeyList();
+    this.onDetected = this.onDetected.bind(this);
+    this.keymapSet = this.keymapSet.bind(this);
+  }
+
+  // delete the instance in state.view
+  deleteRender(instance) {
+    const viewCopy = this.state.view.slice();
+    const index = viewCopy.lastIndexOf(instance);
+    viewCopy.splice(index, 1);
+    this.setState({
+      view: viewCopy,
+    });
+    return null;
+  }
+
+  // this function generates keymap depending on what keys were selected in this.hotkeyCommand
+  keymapSet() {
+    let keymap = this.hotkeyList();
+    keymap = keymap.flat();
+    keymap = new Set(keymap);
+    return Array.from(keymap);
+  }
+
+  // this function generates list of all the hotkeys command
+  hotkeyList() {
+    const hotkeyList = this.supportClasses.map((value) => {
+      return value.getHotkeyStroke();
+    });
+    return hotkeyList;
+  }
+
+  // activates when one of the hotkey strokes gets determined from HotkeysDetector
+  onDetected(strokeDetermined) {
+    let viewDetermined = this.supportClasses.filter((value) => {
+      return strokeDetermined.toString() === value.getHotkeyStroke().toString();
+    });
+    viewDetermined = viewDetermined.map((value) => {
+      return value.getComponent(this.deleteRender);
+    });
+    this.setState({
+      view: this.state.view.concat(viewDetermined).flat(),
+    });
+  }
+
+  render() {
+    return (
+      <React.Fragment>
+        <HotkeysDetector onDetected={stroke => this.onDetected(stroke)} keymap={this.keymap} hotkeyList={this.hotkeyList} />
+        {this.state.view}
+      </React.Fragment>
+    );
+  }
+
+}

+ 59 - 0
src/client/js/components/HotkeysDetector/HotkeysDetector.jsx

@@ -0,0 +1,59 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { GlobalHotKeys } from 'react-hotkeys';
+
+export default class HotkeysDetector extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.hotkeyList = this.props.hotkeyList;
+    this.state = {
+      userCommand: [],
+    };
+    this.processingCommands = [];
+    this.check = this.check.bind(this);
+  }
+
+  check(event) {
+    this.setState({
+      userCommand: this.state.userCommand.concat(event.key),
+    });
+
+    // filters the corresponding hotkeys(keys) that the user has pressed so far
+    const tempUserCommand = this.state.userCommand;
+    this.processingCommands = this.hotkeyList.filter((value) => {
+      return value.slice(0, tempUserCommand.length).toString() === tempUserCommand.toString();
+    });
+
+    // executes if there were keymap that matches what the user pressed fully.
+    if ((this.processingCommands.length === 1) && (this.hotkeyList.find(ary => ary.toString() === this.state.userCommand.toString()))) {
+      this.props.onDetected(this.processingCommands[0]);
+      this.setState({
+        userCommand: [],
+      });
+    }
+    else if (this.processingCommands.toString() === [].toString()) {
+      this.setState({
+        userCommand: [],
+      });
+    }
+    return null;
+
+  }
+
+  render() {
+    const keyMap = { check: this.props.keymap };
+    const handlers = { check: (event) => { return this.check(event) } };
+    return (
+      <GlobalHotKeys keyMap={keyMap} handlers={handlers}>
+      </GlobalHotKeys>
+    );
+  }
+
+}
+
+HotkeysDetector.propTypes = {
+  onDetected: PropTypes.func.isRequired,
+  keymap: PropTypes.array.isRequired,
+  hotkeyList: PropTypes.array.isRequired,
+};

+ 39 - 0
src/client/js/components/MirrorMode/MirrorMode.jsx

@@ -0,0 +1,39 @@
+import React from 'react';
+
+
+/**
+ * Page staff credit component
+ *
+ * @export
+ * @class StaffCredit
+ * @extends {React.Component}
+ */
+
+export default class MirrorMode extends React.Component {
+
+  // when this is called it returns the hotkey stroke
+  static getHotkeyStroke() {
+    return ['x', 'x', 'b', 'b', 'a', 'y', 'a', 'y', 'ArrowDown', 'ArrowLeft'];
+  }
+
+  static getComponent() {
+    return <MirrorMode />;
+  }
+
+  componentDidMount() {
+    const changeBody = document.body;
+    changeBody.classList.add('mirror');
+    return null;
+  }
+
+  render() {
+    return (
+      <React.Fragment>
+      </React.Fragment>
+    );
+  }
+
+}
+
+MirrorMode.propTypes = {
+};

+ 40 - 53
src/client/js/components/StaffCredit/StaffCredit.jsx

@@ -1,16 +1,11 @@
 import React from 'react';
-import { GlobalHotKeys } from 'react-hotkeys';
-
+import PropTypes from 'prop-types';
 import loggerFactory from '@alias/logger';
 import {
   Modal, ModalBody,
 } from 'reactstrap';
-
 import contributors from './Contributor';
 
-// px / sec
-const scrollSpeed = 200;
-
 /**
  * Page staff credit component
  *
@@ -19,56 +14,29 @@ const scrollSpeed = 200;
  * @extends {React.Component}
  */
 
+
 export default class StaffCredit extends React.Component {
 
   constructor(props) {
-    super(props);
 
+    super(props);
     this.logger = loggerFactory('growi:StaffCredit');
-
     this.state = {
-      isShown: false,
-      userCommand: [],
+      isShown: true,
     };
-    this.konamiCommand = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
     this.deleteCredit = this.deleteCredit.bind(this);
   }
 
-  check(event) {
-    this.logger.debug(`'${event.key}' pressed`);
-
-    // compare keydown and next konamiCommand
-    if (this.konamiCommand[this.state.userCommand.length] === event.key) {
-      const nextValue = this.state.userCommand.concat(event.key);
-      if (nextValue.length === this.konamiCommand.length) {
-        this.setState({
-          isShown: true,
-          userCommand: [],
-        });
-        const target = $('.credit-curtain');
-        const scrollTargetHeight = target.children().innerHeight();
-        const duration = scrollTargetHeight / scrollSpeed * 1000;
-        target.animate({ scrollTop: scrollTargetHeight }, duration, 'linear');
-
-        target.slimScroll({
-          height: target.innerHeight(),
-          // Able to scroll after automatic schooling is complete so set "bottom" to allow scrolling from the bottom.
-          start: 'bottom',
-          color: '#FFFFFF',
-        });
-      }
-      else {
-        // add UserCommand
-        this.setState({ userCommand: nextValue });
+  // when this is called it returns the hotkey stroke
+  static getHotkeyStroke() {
+    return ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
+  }
 
-        this.logger.debug('userCommand', this.state.userCommand);
-      }
-    }
-    else {
-      this.setState({ userCommand: [] });
-    }
+  static getComponent(onDeleteRender) {
+    return <StaffCredit onDeleteRender={onDeleteRender} />;
   }
 
+  // to delete the staffCredit and to inform that to Hotkeys.jsx
   deleteCredit() {
     if (this.state.isShown) {
       this.setState({ isShown: false });
@@ -123,21 +91,40 @@ export default class StaffCredit extends React.Component {
     return null;
   }
 
+  componentDidMount() {
+    setTimeout(() => {
+      // px / sec
+      const scrollSpeed = 200;
+      const target = $('.credit-curtain');
+      const scrollTargetHeight = target.children().innerHeight();
+      const duration = scrollTargetHeight / scrollSpeed * 1000;
+      target.animate({ scrollTop: scrollTargetHeight }, duration, 'linear');
+      target.slimScroll({
+        height: target.innerHeight(),
+        // Able to scroll after automatic schooling is complete so set "bottom" to allow scrolling from the bottom.
+        start: 'bottom',
+        color: '#FFFFFF',
+      });
+    }, 10);
+  }
+
   render() {
-    const keyMap = { check: ['up', 'down', 'right', 'left', 'b', 'a'] };
-    const handlers = { check: (event) => { return this.check(event) } };
     return (
-      <GlobalHotKeys keyMap={keyMap} handlers={handlers}>
-        <Modal isOpen={this.state.isShown} toggle={this.deleteCredit} scrollable className="staff-credit">
-          <ModalBody className="credit-curtain">
-            {this.renderContributors()}
-          </ModalBody>
-        </Modal>
-      </GlobalHotKeys>
+      <Modal
+        isOpen={this.state.isShown}
+        onClosed={() => { return () => { this.props.onDeleteRender(this) } }}
+        toggle={this.deleteCredit}
+        scrollable
+        className="staff-credit"
+      >
+        <ModalBody className="credit-curtain">
+          {this.renderContributors()}
+        </ModalBody>
+      </Modal>
     );
   }
 
 }
-
 StaffCredit.propTypes = {
+  onDeleteRender: PropTypes.func,
 };

+ 3 - 0
src/client/styles/scss/_mirror_mode.scss

@@ -0,0 +1,3 @@
+body.mirror {
+  transform: scale(-1, 1);
+}

+ 1 - 0
src/client/styles/scss/style-app.scss

@@ -42,6 +42,7 @@
 @import 'layout_kibela';
 @import 'login';
 @import 'me';
+@import 'mirror_mode';
 @import 'navbar';
 @import 'navbar_kibela';
 @import 'notification';

+ 2 - 2
src/server/views/layout/layout.html

@@ -105,8 +105,8 @@
   </div>
 {% endif %}
 
-<!-- /#staff-credit -->
-<div id="staff-credit"></div>
+<!-- /#hotkeys -->
+<div id="hotkeys"></div>
 
 <div id="page-create-modal"></div>
 {% include '../modal/shortcuts.html' %}

+ 11 - 0
src/server/views/modal/shortcuts.html

@@ -37,6 +37,17 @@
                       <span class="key key-small">B</span>&nbsp;<span class="key key-small">A</span>
                     </td>
                   </tr>
+                  <tr>
+                    <th>{{ t('modal_shortcuts.global.MirrorMode') }}:</th>
+                    <td>
+                      <a href="{{ t('modal_shortcuts.global.konami_code_url') }}" target="_blank">{{ t('modal_shortcuts.global.Konami Code') }}</a><br>
+                      <span class="key key-small">X</span>&nbsp;<span class="key key-small">X</span>
+                      <span class="key key-small">B</span>&nbsp;<span class="key key-small">B</span>
+                      <span class="key key-small">A</span><br><span class="key key-small">Y</span>
+                      <span class="key key-small">A</span>&nbsp;<span class="key key-small">Y</span>
+                      <span class="key key-small">↓</span>&nbsp;<span class="key key-small">←</span>
+                    </td>
+                  </tr>
                 </table>
             </div><!-- /.col-lg-6 -->