Skip to content

Commit 01d74b9

Browse files
authored
Merge pull request #323 from lightningrodlabs/keyboard-nav-preference
Add Preference to Use Coordinates Instead of Modal for Keyboar Navigation
2 parents 6be2445 + 9b6f8ff commit 01d74b9

File tree

7 files changed

+184
-27
lines changed

7 files changed

+184
-27
lines changed

web/src/components/Preferences/Preferences.js

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import './Preferences.scss'
44
import Icon from '../Icon/Icon'
55
import Button from '../Button/Button'
66
import Modal from '../Modal/Modal'
7-
import { MOUSE, TRACKPAD } from '../../redux/ephemeral/local-preferences/reducer'
7+
import {
8+
COORDINATES,
9+
MODAL,
10+
MOUSE,
11+
TRACKPAD,
12+
} from '../../redux/ephemeral/local-preferences/reducer'
813
import PreferenceSelect, {
914
PreferenceSelectExtra,
1015
PreferenceSelectOption,
@@ -52,7 +57,7 @@ function Descriptions({ navigation }) {
5257
)
5358
}
5459

55-
function Internal({ navigation, setNavigationSelected, save }) {
60+
function NavigationModeInternal({ navigation, setNavigationSelected }) {
5661
const options = (
5762
<>
5863
<PreferenceSelectOption
@@ -73,7 +78,6 @@ function Internal({ navigation, setNavigationSelected, save }) {
7378
)
7479
return (
7580
<div className="preferences-content-wrapper">
76-
<div className="preferences-title">Preferences</div>
7781
<PreferenceSelect
7882
iconName="panning.svg"
7983
title="Navigation Mode"
@@ -82,41 +86,87 @@ function Internal({ navigation, setNavigationSelected, save }) {
8286
options={options}
8387
descriptions={<Descriptions navigation={navigation} />}
8488
/>
85-
<div className="preferences-save-button">
86-
<Button onClick={save} text="Save Changes" />
87-
</div>
89+
</div>
90+
)
91+
}
92+
93+
function KeyboardNavigationModeInternal({
94+
keyboardNavigation,
95+
setKeyboardNavigationSelected,
96+
}) {
97+
const options = (
98+
<>
99+
<PreferenceSelectOption
100+
active={keyboardNavigation === MODAL}
101+
onClick={() => setKeyboardNavigationSelected(MODAL)}
102+
iconName="trackpad.svg"
103+
iconExtraClassName="navigation-mode-option-icon-trackpad"
104+
title="Use a Modal"
105+
/>
106+
<PreferenceSelectOption
107+
active={keyboardNavigation === COORDINATES}
108+
onClick={() => setKeyboardNavigationSelected(COORDINATES)}
109+
iconName="mouse.svg"
110+
iconExtraClassName="navigation-mode-option-icon-mouse"
111+
title="Use Coordinates"
112+
/>
113+
</>
114+
)
115+
return (
116+
<div className="preferences-content-wrapper">
117+
<PreferenceSelect
118+
iconName="panning.svg"
119+
title="Keyboard Navigation Mode"
120+
subtitle="Select your preferred method of using the keyboard to navigate from parent to child, and vice-versa"
121+
options={options}
122+
/>
88123
</div>
89124
)
90125
}
91126

92127
export default function Preferences({
93128
navigation,
129+
keyboardNavigation,
94130
setNavigationPreference,
131+
setKeyboardNavigationPreference,
95132
showPreferences,
96133
setShowPreferences,
97134
}) {
98135
// hold an internal version of the preferences state, so that we can toggle it, before saving it
99136
const [navigationSelected, setNavigationSelected] = useState(navigation)
137+
const [keyboardNavigationSelected, setKeyboardNavigationSelected] = useState(
138+
keyboardNavigation
139+
)
100140

101141
const save = () => {
102142
setNavigationPreference(navigationSelected)
143+
setKeyboardNavigationPreference(keyboardNavigationSelected)
103144
setShowPreferences(false)
104145
}
105146
const close = () => {
106147
// reset navigation selected to
107148
// whatever the canonical state of navigation preference
108149
// is equal to
109150
setNavigationSelected(navigation)
151+
setKeyboardNavigationSelected(keyboardNavigation)
110152
setShowPreferences(false)
111153
}
112154

113155
return (
114156
<Modal white active={showPreferences} onClose={close}>
115-
<Internal
157+
<div className="preferences-title">Preferences</div>
158+
<NavigationModeInternal
116159
navigation={navigationSelected}
117160
setNavigationSelected={setNavigationSelected}
118-
save={save}
119161
/>
162+
<KeyboardNavigationModeInternal
163+
keyboardNavigation={keyboardNavigationSelected}
164+
setKeyboardNavigationSelected={setKeyboardNavigationSelected}
165+
/>
166+
167+
<div className="preferences-save-button">
168+
<Button onClick={save} text="Save Changes" />
169+
</div>
120170
</Modal>
121171
)
122172
}

web/src/event-listeners/index.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ import {
4545
closeExpandedView,
4646
openExpandedView,
4747
} from '../redux/ephemeral/expanded-view/actions'
48-
import { MOUSE, TRACKPAD } from '../redux/ephemeral/local-preferences/reducer'
48+
import {
49+
COORDINATES,
50+
MOUSE,
51+
TRACKPAD,
52+
} from '../redux/ephemeral/local-preferences/reducer'
4953

5054
import { setOutcomeClone } from '../redux/ephemeral/outcome-clone/actions'
5155

@@ -116,6 +120,15 @@ function handleMouseUpForOutcomeForm({
116120
)
117121
}
118122

123+
function leftMostOutcome(
124+
outcomeActionHashes: ActionHashB64[],
125+
state: RootState
126+
): ActionHashB64 {
127+
return _.minBy(outcomeActionHashes, (actionHash) => {
128+
return state.ui.layout.coordinates[actionHash].x
129+
})
130+
}
131+
119132
// outcomes is ComputedOutcomes in an object, keyed by their actionHash
120133
export default function setupEventListeners(
121134
store: any,
@@ -141,6 +154,10 @@ export default function setupEventListeners(
141154
)
142155
}
143156

157+
function getKeyboardNavigationPreference(state: RootState): string {
158+
return state.ui.localPreferences.keyboardNavigation
159+
}
160+
144161
function panAndZoom(actionHash: string) {
145162
store.dispatch(animatePanAndZoom(actionHash, false))
146163
}
@@ -177,9 +194,14 @@ export default function setupEventListeners(
177194
)
178195
if (childrenActionHashes.length) {
179196
event.stopPropagation()
180-
if (childrenActionHashes.length === 1)
197+
const keyboardNavPreference = getKeyboardNavigationPreference(state)
198+
if (childrenActionHashes.length === 1) {
181199
panAndZoom(childrenActionHashes[0])
182-
else {
200+
} else if (keyboardNavPreference === COORDINATES) {
201+
// navigate to the left-most child
202+
const leftMostChild = leftMostOutcome(childrenActionHashes, state)
203+
panAndZoom(leftMostChild)
204+
} else {
183205
store.dispatch(setNavModalOpenChildren(childrenActionHashes))
184206
}
185207
}
@@ -196,9 +218,14 @@ export default function setupEventListeners(
196218
)
197219
if (parentsActionHashes.length) {
198220
event.stopPropagation()
199-
if (parentsActionHashes.length === 1)
221+
const keyboardNavPreference = getKeyboardNavigationPreference(state)
222+
if (parentsActionHashes.length === 1) {
200223
panAndZoom(parentsActionHashes[0])
201-
else {
224+
} else if (keyboardNavPreference === COORDINATES) {
225+
// navigate to the left most parent
226+
const leftMostParent = leftMostOutcome(parentsActionHashes, state)
227+
panAndZoom(leftMostParent)
228+
} else {
202229
store.dispatch(setNavModalOpenParents(parentsActionHashes))
203230
}
204231
}
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
const SET_NAVIGATION_PREFERENCE = 'set_navigation_preference'
2+
const SET_KEYBOARD_NAVIGATION_PREFERENCE = 'set_keyboard_navigation_preference'
23

3-
function setNavigationPreference (preference) {
4+
function setNavigationPreference(preference) {
45
return {
56
type: SET_NAVIGATION_PREFERENCE,
6-
payload: preference
7+
payload: preference,
8+
}
9+
}
10+
11+
function setKeyboardNavigationPreference(preference) {
12+
return {
13+
type: SET_KEYBOARD_NAVIGATION_PREFERENCE,
14+
payload: preference,
715
}
816
}
917

1018
export {
1119
SET_NAVIGATION_PREFERENCE,
20+
SET_KEYBOARD_NAVIGATION_PREFERENCE,
1221
setNavigationPreference,
22+
setKeyboardNavigationPreference,
1323
}

web/src/redux/ephemeral/local-preferences/reducer.js renamed to web/src/redux/ephemeral/local-preferences/reducer.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { SET_NAVIGATION_PREFERENCE } from './actions'
1+
import {
2+
SET_KEYBOARD_NAVIGATION_PREFERENCE,
3+
SET_NAVIGATION_PREFERENCE,
4+
} from './actions'
25

36
const LOCAL_STORAGE_PREFIX = 'acorn-'
47
const getLocalItem = (key) => {
@@ -8,12 +11,21 @@ const setLocalItem = (key, value) => {
811
localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${key}`, value)
912
}
1013

14+
const KEYBOARD_NAV_KEY = 'keyboardNavigationPreference'
15+
export const COORDINATES = 'coordinates'
16+
export const MODAL = 'modal'
17+
export type KeyboardNavigationPreference = typeof COORDINATES | typeof MODAL
18+
1119
const NAV_KEY = 'navigationPreference'
1220
export const MOUSE = 'mouse'
1321
export const TRACKPAD = 'trackpad'
22+
export type NavigationPreference = typeof MOUSE | typeof TRACKPAD
1423

1524
const defaultState = {
25+
// default to trackpad navigation
1626
navigation: getLocalItem(NAV_KEY) || TRACKPAD,
27+
// default to modal keyboard navigation
28+
keyboardNavigation: getLocalItem(KEYBOARD_NAV_KEY) || MODAL,
1729
}
1830

1931
export default function (state = defaultState, action) {
@@ -27,6 +39,13 @@ export default function (state = defaultState, action) {
2739
...state,
2840
navigation: payload,
2941
}
42+
43+
case SET_KEYBOARD_NAVIGATION_PREFERENCE:
44+
setLocalItem(KEYBOARD_NAV_KEY, payload)
45+
return {
46+
...state,
47+
keyboardNavigation: payload,
48+
}
3049
default:
3150
return state
3251
}

web/src/routes/App.component.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ import useFinishMigrationChecker from '../hooks/useFinishMigrationChecker'
4040
import useFileDownloaded from '../hooks/useFileDownloaded'
4141
import UpdateModalContext from '../context/UpdateModalContext'
4242
import { ViewingReleaseNotes } from '../components/UpdateModal/UpdateModal'
43+
import {
44+
COORDINATES,
45+
KeyboardNavigationPreference,
46+
MODAL,
47+
MOUSE,
48+
NavigationPreference,
49+
TRACKPAD,
50+
} from '../redux/ephemeral/local-preferences/reducer.js'
4351

4452
export type AppStateProps = {
4553
profilesCellIdString: string
@@ -54,7 +62,8 @@ export type AppStateProps = {
5462
agentAddress: AgentPubKeyB64
5563
whoami: WireRecord<Profile>
5664
hasFetchedForWhoami: boolean
57-
navigationPreference: 'mouse' | 'trackpad'
65+
navigationPreference: NavigationPreference
66+
keyboardNavigationPreference: KeyboardNavigationPreference
5867
inviteMembersModalShowing: null | string // will be the passphrase if defined
5968
hasMigratedSharedProject: boolean
6069
hiddenAchievedOutcomes: CellIdString[]
@@ -64,7 +73,10 @@ export type AppStateProps = {
6473

6574
export type AppDispatchProps = {
6675
dispatch: any
67-
setNavigationPreference: (preference: 'mouse' | 'trackpad') => void
76+
setNavigationPreference: (preference: NavigationPreference) => void
77+
setKeyboardNavigationPreference: (
78+
preference: typeof COORDINATES | typeof MODAL
79+
) => void
6880
hideInviteMembersModal: () => void
6981
openInviteMembersModal: (passphrase: string) => void
7082
goToOutcome: (outcomeActionHash: ActionHashB64) => void
@@ -95,6 +107,8 @@ const App: React.FC<AppProps> = ({
95107
updateWhoami,
96108
navigationPreference,
97109
setNavigationPreference,
110+
keyboardNavigationPreference,
111+
setKeyboardNavigationPreference,
98112
inviteMembersModalShowing,
99113
openInviteMembersModal,
100114
hideInviteMembersModal,
@@ -213,7 +227,12 @@ const App: React.FC<AppProps> = ({
213227
<Route
214228
path="/run-update"
215229
render={() => (
216-
<VersionUpdateLeaving updateVersionInfo={updateVersionInfo} triggerAMigrationCheck={finishMigrationChecker.triggerACheck} />
230+
<VersionUpdateLeaving
231+
updateVersionInfo={updateVersionInfo}
232+
triggerAMigrationCheck={
233+
finishMigrationChecker.triggerACheck
234+
}
235+
/>
217236
)}
218237
/>
219238
<Route
@@ -222,7 +241,9 @@ const App: React.FC<AppProps> = ({
222241
<VersionUpdateEntering
223242
hasCheckedForMigration={finishMigrationChecker.hasChecked}
224243
migrationData={finishMigrationChecker.dataForNeedsMigration}
225-
migrationDataFileName={finishMigrationChecker.migrationDataFileName}
244+
migrationDataFileName={
245+
finishMigrationChecker.migrationDataFileName
246+
}
226247
/>
227248
)}
228249
/>
@@ -237,6 +258,8 @@ const App: React.FC<AppProps> = ({
237258
agentAddress,
238259
navigationPreference,
239260
setNavigationPreference,
261+
keyboardNavigationPreference,
262+
setKeyboardNavigationPreference,
240263
showProfileEditForm,
241264
setShowProfileEditForm,
242265
showPreferences,

web/src/routes/App.connector.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { ActionHashB64 } from '../types/shared'
44
import { LayeringAlgorithm, Profile } from '../types'
55

66
import { updateWhoami } from '../redux/persistent/profiles/who-am-i/actions'
7-
import { setNavigationPreference } from '../redux/ephemeral/local-preferences/actions'
7+
import {
8+
setKeyboardNavigationPreference,
9+
setNavigationPreference,
10+
} from '../redux/ephemeral/local-preferences/actions'
811
import selectEntryPoints, {
912
selectActiveProjectMembers,
1013
} from '../redux/persistent/projects/entry-points/select'
@@ -35,7 +38,7 @@ function mapStateToProps(state: RootState): AppStateProps {
3538
activeProject,
3639
activeEntryPoints,
3740
inviteMembersModal,
38-
localPreferences: { navigation },
41+
localPreferences: { navigation, keyboardNavigation },
3942
},
4043
cells: { profiles: profilesCellIdString },
4144
} = state
@@ -74,7 +77,7 @@ function mapStateToProps(state: RootState): AppStateProps {
7477

7578
const selectedLayeringAlgo = activeProjectMeta
7679
? activeProjectMeta.layeringAlgorithm
77-
: "LongestPath"
80+
: 'LongestPath'
7881

7982
return {
8083
profilesCellIdString,
@@ -85,6 +88,7 @@ function mapStateToProps(state: RootState): AppStateProps {
8588
hasFetchedForWhoami,
8689
agentAddress: state.agentAddress,
8790
navigationPreference: navigation,
91+
keyboardNavigationPreference: keyboardNavigation,
8892
inviteMembersModalShowing: inviteMembersModal.passphrase,
8993
members,
9094
presentMembers,
@@ -101,6 +105,9 @@ function mapDispatchToProps(dispatch): AppDispatchProps {
101105
setNavigationPreference: (preference) => {
102106
return dispatch(setNavigationPreference(preference))
103107
},
108+
setKeyboardNavigationPreference: (preference) => {
109+
return dispatch(setKeyboardNavigationPreference(preference))
110+
},
104111
goToOutcome: (outcomeActionHash) => {
105112
return dispatch(animatePanAndZoom(outcomeActionHash, true))
106113
},

0 commit comments

Comments
 (0)