diff --git a/web/actions/actions.dart b/web/actions/actions.dart index 14b7f2e..9a32c6d 100644 --- a/web/actions/actions.dart +++ b/web/actions/actions.dart @@ -2,12 +2,13 @@ library gridhub.actions; import 'package:flux/flux.dart'; +part './global_state_actions.dart'; part './repo_actions.dart'; - class GridHubActions { - RepoActions repoActions; + final GlobalStateActions globalStateActions = new GlobalStateActions(); + final RepoActions repoActions = new RepoActions(); - GridHubActions(this.repoActions); + GridHubActions(); } diff --git a/web/actions/global_state_actions.dart b/web/actions/global_state_actions.dart new file mode 100644 index 0000000..0c06768 --- /dev/null +++ b/web/actions/global_state_actions.dart @@ -0,0 +1,11 @@ +part of gridhub.actions; + + +class GlobalStateActions { + + /// Payload is whether or not the global open state is open or closed + final Action globalOpenState = new Action(); + + /// Payload is active pane key + final Action switchActivePaneKey = new Action(); +} \ No newline at end of file diff --git a/web/actions/repo_actions.dart b/web/actions/repo_actions.dart index a62aecc..5229a9c 100644 --- a/web/actions/repo_actions.dart +++ b/web/actions/repo_actions.dart @@ -28,6 +28,7 @@ class RepoActions { /// Payload is the page name to switch to final Action switchPage = new Action(); - /// Payload is whether or not the global open state is open or closed - final Action globalOpenState = new Action(); + /// Payload is the page index to switch to + final Action switchPageByIndex = new Action(); + } diff --git a/web/components/GridHubApp.dart b/web/components/GridHubApp.dart index 7942825..6318871 100644 --- a/web/components/GridHubApp.dart +++ b/web/components/GridHubApp.dart @@ -4,6 +4,7 @@ import 'package:flux/flux.dart'; import 'package:react/react.dart' as react; import 'package:web_skin_react/web_skin_react.dart'; +import '../actions/actions.dart'; import '../models/repo.dart'; import '../stores/stores.dart'; @@ -16,7 +17,7 @@ import 'GridHubHeader.dart'; * The GridHub Application component */ var GridHubApp = react.registerComponent(() => new _GridHubApp()); -class _GridHubApp extends FluxComponent { +class _GridHubApp extends FluxComponent { String get currentPage => this.state['currentPage']; String get globalActiveKey => this.state['globalActiveKey']; @@ -34,24 +35,25 @@ class _GridHubApp extends FluxComponent { }; } - globalButtonClicked(activeKey) { - return (eventKey, href, target) { - this.setState({'globalActiveKey': activeKey}); - }; + _globalStateStoreUpdated(GlobalStateStore store) { + this.setState({ + 'globalActiveKey': store.activePaneKey, + 'openState': store.openState + }); } _repoStoreUpdated(ReposStore store) { this.setState({ 'repos': store.currentPageRepos, 'currentPage': store.currentPage, - 'openState': store.openState, 'pageNames': store.pageNames }); } getStoreHandlers() { return { - stores.reposStore: _repoStoreUpdated + stores.reposStore: _repoStoreUpdated, + stores.globalStateStore: _globalStateStoreUpdated }; } @@ -87,7 +89,6 @@ class _GridHubApp extends FluxComponent { GridHubHeader({ 'actions': actions, 'currentPage': currentPage, - 'globalButtonClickHandler': this.globalButtonClicked, 'pageNames': pageNames, 'key': 'header' }), diff --git a/web/components/GridHubHeader.dart b/web/components/GridHubHeader.dart index a133596..dd4b2f4 100644 --- a/web/components/GridHubHeader.dart +++ b/web/components/GridHubHeader.dart @@ -24,7 +24,6 @@ class _GridHubHeader extends react.Component { return { 'actions': null, 'currentPage': '', - 'globalButtonClickHandler': (){}, 'pageNames': [] }; } @@ -97,12 +96,18 @@ class _GridHubHeader extends react.Component { onOpenState(event) { event.preventDefault(); - actions.repoActions.globalOpenState.dispatch(true); + actions.globalStateActions.globalOpenState.dispatch(true); } onCloseState(event) { event.preventDefault(); - actions.repoActions.globalOpenState.dispatch(false); + actions.globalStateActions.globalOpenState.dispatch(false); + } + + onSwitchActivePane(String key) { + return (event, href, target) { + actions.globalStateActions.switchActivePaneKey.dispatch(key); + }; } render() { @@ -111,7 +116,6 @@ class _GridHubHeader extends react.Component { if (editPageName == null) { editPageName = currentPage; } - var globalButtonClickHandler = this.props['globalButtonClickHandler']; var githubUsername = this.state['githubUsername']; var githubAccessToken = this.state['githubAccessToken']; var newPageName = this.state['newPageName']; @@ -215,12 +219,12 @@ class _GridHubHeader extends react.Component { ), // GLOBAL STATE BUTTONS - NavItem({'onSelect': globalButtonClickHandler('1'), 'key': 'readme-icon-nav-item'}, readmeIcon), - NavItem({'onSelect': globalButtonClickHandler('2'), 'key': 'tag-icon-nav-item'}, tagIcon), - NavItem({'onSelect': globalButtonClickHandler('3'), 'key': 'issue-icon-nav-item'}, issueIcon), - NavItem({'onSelect': globalButtonClickHandler('4'), 'key': 'pull-icon-nav-item'}, pullRequestIcon), - NavItem({'onSelect': globalButtonClickHandler('5'), 'key': 'unreleased-icon-nav-item'}, unreleasedIcon), - NavItem({'onSelect': globalButtonClickHandler('6'), 'key': 'milestone-icon-nav-item'}, milestonesIcon), + NavItem({'onSelect': onSwitchActivePane('1'), 'key': 'readme-icon-nav-item'}, readmeIcon), + NavItem({'onSelect': onSwitchActivePane('2'), 'key': 'tag-icon-nav-item'}, tagIcon), + NavItem({'onSelect': onSwitchActivePane('3'), 'key': 'issue-icon-nav-item'}, issueIcon), + NavItem({'onSelect': onSwitchActivePane('4'), 'key': 'pull-icon-nav-item'}, pullRequestIcon), + NavItem({'onSelect': onSwitchActivePane('5'), 'key': 'unreleased-icon-nav-item'}, unreleasedIcon), + NavItem({'onSelect': onSwitchActivePane('6'), 'key': 'milestone-icon-nav-item'}, milestonesIcon), react.li({'className': 'nav-item nav-item-text-button', 'onClick': onOpenState, 'key': 'open-button-nav-item'}, Button({'wsSize': 'xsmall', 'wsStyle': null, 'className': 'hitarea'}, 'Open')), diff --git a/web/gridhub.dart b/web/gridhub.dart index de0da8b..6b515bb 100644 --- a/web/gridhub.dart +++ b/web/gridhub.dart @@ -7,6 +7,7 @@ import 'actions/actions.dart'; import 'components/GridHubApp.dart' show GridHubApp; import 'services/localStorageService.dart' as localStorageService; import 'stores/stores.dart'; +import 'utils/keyboard_shortcuts.dart'; void main() { @@ -16,16 +17,19 @@ void main() { reactClient.setClientConfiguration(); // Initialize actions - GridHubActions actions = new GridHubActions( - new RepoActions() - ); + GridHubActions actions = new GridHubActions(); // Initialize data layer and stores var storage = new localStorageService.RepoGridData(); GridHubStores stores = new GridHubStores( - new ReposStore(actions, storage) + new ReposStore(actions, storage), + new GlobalStateStore(actions) ); // Render the application react.render(GridHubApp({'actions': actions, 'stores': stores}), domContainer); + + document.onKeyDown.listen((KeyboardEvent event) { + handleKeyDown(event, actions); + }); } diff --git a/web/stores/global_state_store.dart b/web/stores/global_state_store.dart new file mode 100644 index 0000000..b690d65 --- /dev/null +++ b/web/stores/global_state_store.dart @@ -0,0 +1,27 @@ +part of gridhub.stores; + +class GlobalStateStore extends Store { + + GridHubActions _actions; + String _activePaneKey = '1'; + bool _openState = true; + + String get activePaneKey => _activePaneKey; + bool get openState => _openState; + + GlobalStateStore(GridHubActions this._actions) { + _actions.globalStateActions.globalOpenState.listen(onGlobalOpenState); + _actions.globalStateActions.switchActivePaneKey.listen(onSwitchActivePaneKey); + } + + + onGlobalOpenState(bool open) { + _openState = open; + trigger(); + } + + onSwitchActivePaneKey(String key) { + _activePaneKey = key; + trigger(); + } +} diff --git a/web/stores/repos_store.dart b/web/stores/repos_store.dart index c5e288d..b9c2105 100644 --- a/web/stores/repos_store.dart +++ b/web/stores/repos_store.dart @@ -6,10 +6,8 @@ class ReposStore extends Store { // Private data Map> _allRepos; RepoGridData _storage; - bool _openState; // Public data - bool get openState => _openState; String get currentPage => _storage.currentPage; List get currentPageRepos => _allRepos[currentPage]; List get pageNames => _storage.pageNames; @@ -21,7 +19,6 @@ class ReposStore extends Store { _actions = actions.repoActions; _storage = storage; _allRepos = {}; - _openState = true; initializeCurrentPageRepos(); @@ -36,7 +33,7 @@ class ReposStore extends Store { _actions.editPage.listen(onEditPage); _actions.refreshPage.listen(onRefreshPage); _actions.switchPage.listen(onSwitchPage); - _actions.globalOpenState.listen(onGlobalOpenState); + _actions.switchPageByIndex.listen(onSwitchPageByIndex); } Future initializeCurrentPageRepos() { @@ -132,9 +129,14 @@ class ReposStore extends Store { } } - onGlobalOpenState(bool open) { - _openState = open; - trigger(); + onSwitchPageByIndex(int index) { + if (pageNames.length < index + 1) { + // If there aren't enough pages, go to last page + onSwitchPage(pageNames.last); + } + else { + onSwitchPage(pageNames[index]); + } } _getPayload(toCall) { diff --git a/web/stores/stores.dart b/web/stores/stores.dart index 4b2c5e7..2761c4b 100644 --- a/web/stores/stores.dart +++ b/web/stores/stores.dart @@ -7,12 +7,14 @@ import '../actions/actions.dart'; import '../models/repo.dart'; import '../services/localStorageService.dart'; +part './global_state_store.dart'; part './repos_store.dart'; class GridHubStores { + GlobalStateStore globalStateStore; ReposStore reposStore; - GridHubStores(this.reposStore); + GridHubStores(this.reposStore, this.globalStateStore); } diff --git a/web/utils/keyboard_shortcuts.dart b/web/utils/keyboard_shortcuts.dart new file mode 100644 index 0000000..0475475 --- /dev/null +++ b/web/utils/keyboard_shortcuts.dart @@ -0,0 +1,75 @@ +library gridhub.utils.keyboard_shortcuts; + +import 'dart:html'; + +import '../actions/actions.dart'; + +void handleKeyDown(KeyboardEvent event, GridHubActions gridhubActions) { + RepoActions actions = gridhubActions.repoActions; + switch (event.keyCode) { + // Pages + case KeyCode.ONE: + actions.switchPageByIndex.dispatch(0); + break; + case KeyCode.TWO: + actions.switchPageByIndex.dispatch(1); + break; + case KeyCode.THREE: + actions.switchPageByIndex.dispatch(2); + break; + case KeyCode.FOUR: + actions.switchPageByIndex.dispatch(3); + break; + case KeyCode.FIVE: + actions.switchPageByIndex.dispatch(4); + break; + case KeyCode.SIX: + actions.switchPageByIndex.dispatch(5); + break; + case KeyCode.SEVEN: + actions.switchPageByIndex.dispatch(6); + break; + case KeyCode.EIGHT: + actions.switchPageByIndex.dispatch(7); + break; + case KeyCode.NINE: + actions.switchPageByIndex.dispatch(8); + break; + case KeyCode.ZERO: + actions.switchPageByIndex.dispatch(9); + break; + + // Panels + case KeyCode.A: + gridhubActions.globalStateActions.switchActivePaneKey.dispatch('1'); + break; + case KeyCode.S: + gridhubActions.globalStateActions.switchActivePaneKey.dispatch('2'); + break; + case KeyCode.D: + gridhubActions.globalStateActions.switchActivePaneKey.dispatch('3'); + break; + case KeyCode.F: + gridhubActions.globalStateActions.switchActivePaneKey.dispatch('4'); + break; + case KeyCode.G: + gridhubActions.globalStateActions.switchActivePaneKey.dispatch('5'); + break; + case KeyCode.H: + gridhubActions.globalStateActions.switchActivePaneKey.dispatch('6'); + break; + + // Open/Close + case KeyCode.O: + gridhubActions.globalStateActions.globalOpenState.dispatch(true); + break; + case KeyCode.P: + gridhubActions.globalStateActions.globalOpenState.dispatch(false); + break; + + // Refresh + case KeyCode.R: + actions.refreshPage.dispatch(); + break; + } +}