From effa7471a47a9a182c65cb6020a8aacb23543e8a Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 13:47:27 +1000 Subject: [PATCH 01/11] BigIntOrderedMap: faster --- pkg/npm/api/lib/BigIntOrderedMap.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/npm/api/lib/BigIntOrderedMap.ts b/pkg/npm/api/lib/BigIntOrderedMap.ts index c5e26da2cc..d8202e4bd0 100644 --- a/pkg/npm/api/lib/BigIntOrderedMap.ts +++ b/pkg/npm/api/lib/BigIntOrderedMap.ts @@ -39,7 +39,7 @@ export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { items.forEach(([key, value]) => { draft.root[key.toString()] = castDraft(value); }); - draft.generateCachedIter(); + draft.cachedIter = null; }, (patches) => { //console.log(`gassed with ${JSON.stringify(patches, null, 2)}`); @@ -49,7 +49,7 @@ export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { set(key: BigInteger, value: V) { return produce(this, draft => { draft.root[key.toString()] = castDraft(value); - draft.generateCachedIter(); + draft.cachedIter = null; }); } @@ -67,13 +67,13 @@ export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { delete(key: BigInteger) { return produce(this, draft => { delete draft.root[key.toString()]; - draft.cachedIter = draft.cachedIter.filter(([x]) => x.eq(key)); + draft.cachedIter = null; }); } [Symbol.iterator](): IterableIterator<[BigInteger, V]> { let idx = 0; - let result = [...this.cachedIter]; + let result = this.generateCachedIter(); return { [Symbol.iterator]: this[Symbol.iterator], next: (): IteratorResult<[BigInteger, V]> => { @@ -100,11 +100,15 @@ export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { } generateCachedIter() { + if(this.cachedIter) { + return [...this.cachedIter]; + } const result = Object.keys(this.root).map(key => { const num = bigInt(key); return [num, this.root[key]] as [BigInteger, V]; }).sort(([a], [b]) => sortBigInt(a,b)); this.cachedIter = result; + return result; } } From 018a17fc6f9a7fab3e2507b69b468e9b980429bc Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 13:48:07 +1000 Subject: [PATCH 02/11] interface: add shorcut context --- .../src/logic/lib/shortcutContext.tsx | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 pkg/interface/src/logic/lib/shortcutContext.tsx diff --git a/pkg/interface/src/logic/lib/shortcutContext.tsx b/pkg/interface/src/logic/lib/shortcutContext.tsx new file mode 100644 index 0000000000..2108a300c3 --- /dev/null +++ b/pkg/interface/src/logic/lib/shortcutContext.tsx @@ -0,0 +1,76 @@ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react'; +import _ from 'lodash'; + +type Handler = (e: KeyboardEvent) => void; +const fallback: ShortcutContextProps = { + add: () => {}, + remove: () => {}, +}; +const getChord = (e: KeyboardEvent) => { + let chord = [e.key]; + if(e.metaKey) { + chord.unshift('meta'); + } + if(e.ctrlKey) { + chord.unshift('ctrl'); + } + return chord.join('+'); +} + +export const ShortcutContext = createContext(fallback); +export interface ShortcutContextProps { + add: (cb: (e: KeyboardEvent) => void, key: string) => void; + remove: (cb: (e: KeyboardEvent) => void, key: string) => void; +} +export function ShortcutContextProvider({ children }) { + const [shortcuts, setShortcuts] = useState({} as Record); + const handlerRef = useRef(() => {}); + + const add = useCallback((cb: Handler, key: string) => { + setShortcuts((s) => ({ ...s, [key]: cb })); + }, []); + const remove = useCallback((cb: Handler, key: string) => { + setShortcuts((s) => (key in s ? _.omit(s, key) : s)); + }, []); + + useEffect(() => { + function onKeypress(e: KeyboardEvent) { + handlerRef.current(e); + } + document.addEventListener('keypress', onKeypress); + return () => { + document.removeEventListener('keypress', onKeypress); + }; + }, []); + + useEffect(() => { + handlerRef.current = function (e: KeyboardEvent) { + const chord = getChord(e); + console.log(chord); + shortcuts?.[chord]?.(e); + }; + }, [shortcuts]); + + return ( + + {children} + + ); +} + +export function useShortcut(key: string, cb: Handler) { + const { add, remove } = useContext(ShortcutContext); + useEffect(() => { + add(cb, key); + return () => { + remove(cb, key); + }; + }, [key, cb]); +} From 36798b62ee14c59ac264b8d922a75c005cba660a Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 13:56:53 +1000 Subject: [PATCH 03/11] chat-editor: focus on alphanumeric keystroke --- .../views/apps/chat/components/chat-editor.js | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/components/chat-editor.js b/pkg/interface/src/views/apps/chat/components/chat-editor.js index df87abe5ed..862a38a9a6 100644 --- a/pkg/interface/src/views/apps/chat/components/chat-editor.js +++ b/pkg/interface/src/views/apps/chat/components/chat-editor.js @@ -108,10 +108,27 @@ export default class ChatEditor extends Component { message: props.message }; this.editor = null; + this.onKeyPress = this.onKeyPress.bind(this); + } + + componentDidMount() { + document.addEventListener('keydown', this.onKeyPress); } componentWillUnmount() { this.props.onUnmount(this.state.message); + document.removeEventListener('keydown', this.onKeyPress); + } + + onKeyPress(e) { + const focusedTag = document.activeElement?.nodeName?.toLowerCase(); + const shouldCapture = !(focusedTag === 'textarea' || focusedTag === 'input' || e.metaKey || e.ctrlKey); + if(/^[a-z]|[A-Z]$/.test(e.key) && shouldCapture) { + this.editor.focus(); + } + if(e.key === 'Escape') { + this.editor.getInputField().blur(); + } } componentDidUpdate(prevProps) { @@ -120,9 +137,9 @@ export default class ChatEditor extends Component { if (prevProps.message !== props.message) { this.editor.setValue(props.message); this.editor.setOption('mode', MARKDOWN_CONFIG); - this.editor?.focus(); - this.editor.execCommand('goDocEnd'); - this.editor?.focus(); + //this.editor?.focus(); + //this.editor.execCommand('goDocEnd'); + //this.editor?.focus(); return; } @@ -260,7 +277,6 @@ export default class ChatEditor extends Component { onChange={(e, d, v) => this.messageChange(e, d, v)} editorDidMount={(editor) => { this.editor = editor; - editor.focus(); }} {...props} /> From b3ee418b3f63e49fd49148a557d80142752f57ba Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 13:57:34 +1000 Subject: [PATCH 04/11] interface: add shortcut provider --- pkg/interface/src/views/App.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/interface/src/views/App.js b/pkg/interface/src/views/App.js index 9e14446a27..50c98f5da7 100644 --- a/pkg/interface/src/views/App.js +++ b/pkg/interface/src/views/App.js @@ -36,6 +36,7 @@ import useContactState from '~/logic/state/contact'; import useGroupState from '~/logic/state/group'; import useSettingsState from '~/logic/state/settings'; import gcpManager from '~/logic/lib/gcpManager'; +import { ShortcutContextProvider } from '~/logic/lib/shortcutContext'; const Root = withState(styled.div` @@ -151,6 +152,7 @@ class App extends React.Component { const ourContact = this.props.contacts[`~${this.ship}`] || null; return ( + {window.ship.length < 14 ? @@ -188,6 +190,7 @@ class App extends React.Component {
+ ); } From 9e6862f1c9988d291e266a4e7556a4114caab87d Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 13:58:24 +1000 Subject: [PATCH 05/11] skeleton: show/hide sidebar --- .../src/views/landscape/components/Skeleton.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/views/landscape/components/Skeleton.tsx b/pkg/interface/src/views/landscape/components/Skeleton.tsx index ea1280a92e..dddd8dfdc6 100644 --- a/pkg/interface/src/views/landscape/components/Skeleton.tsx +++ b/pkg/interface/src/views/landscape/components/Skeleton.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, ReactNode, useMemo } from 'react'; +import React, { Children, ReactElement, ReactNode, useCallback, useMemo, useState } from 'react'; import { Groups, Graphs, Invites, Rolodex, Path, AppName } from '@urbit/api'; import { Associations } from '@urbit/api/metadata'; @@ -11,6 +11,7 @@ import { Workspace } from '~/types/workspace'; import useGraphState from '~/logic/state/graph'; import useHarkState from '~/logic/state/hark'; import ErrorBoundary from '~/views/components/ErrorBoundary'; +import {useShortcut} from '~/logic/lib/shortcutContext'; interface SkeletonProps { children: ReactNode; @@ -24,6 +25,10 @@ interface SkeletonProps { } export function Skeleton(props: SkeletonProps): ReactElement { + const [sidebar, setSidebar] = useState(true) + useShortcut('ctrl+h', useCallback(() => { + setSidebar(s => !s); + }, [])); const graphs = useGraphState(state => state.graphs); const graphKeys = useGraphState(state => state.graphKeys); const unreads = useHarkState(state => state.unreads); @@ -35,7 +40,7 @@ export function Skeleton(props: SkeletonProps): ReactElement { [graphConfig] ); - return ( + return !sidebar ? ( {props.children} ) : ( Date: Wed, 5 May 2021 13:59:06 +1000 Subject: [PATCH 06/11] interface: add global nav shortcuts --- .../src/views/landscape/components/Content.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/views/landscape/components/Content.js b/pkg/interface/src/views/landscape/components/Content.js index 72fcf0b4e3..21431df2ef 100644 --- a/pkg/interface/src/views/landscape/components/Content.js +++ b/pkg/interface/src/views/landscape/components/Content.js @@ -1,6 +1,6 @@ -import React, { Component, useEffect } from 'react'; +import React, { Component, useCallback, useEffect } from 'react'; import { Box } from '@tlon/indigo-react'; -import { Route, Switch } from 'react-router-dom'; +import { useHistory, Route, Switch } from 'react-router-dom'; import styled from 'styled-components'; import LaunchApp from '~/views/apps/launch/app'; @@ -14,6 +14,8 @@ import GraphApp from '../../apps/graph/app'; import { PermalinkRoutes } from '~/views/apps/permalinks/app'; import { useLocalStorageState } from '~/logic/lib/useLocalStorageState'; +import { useShortcuts } from '~/logic/lib/useShortcuts'; +import { useShortcut } from '~/logic/lib/shortcutContext'; export const Container = styled(Box)` @@ -25,6 +27,20 @@ export const Container = styled(Box)` export const Content = (props) => { + const history = useHistory(); + + useShortcut('ctrl+f', useCallback((e) => { + e.preventDefault(); + e.stopImmediatePropagation(); + history.goForward(); + }, [history.goBack])); + + useShortcut('ctrl+b', useCallback((e) => { + e.preventDefault(); + e.stopImmediatePropagation(); + history.goBack(); + }, [history.goBack])); + const [hasProtocol, setHasProtocol] = useLocalStorageState( 'registeredProtocol', false From 88e5f297c9619cd8dff28c8ed3a9140de3f803de Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 13:59:44 +1000 Subject: [PATCH 07/11] Sidebar: add cycle shortcuts --- pkg/interface/src/logic/lib/util.ts | 19 +++++++++++ .../components/Sidebar/SidebarList.tsx | 32 +++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/logic/lib/util.ts b/pkg/interface/src/logic/lib/util.ts index eb79f2bcdd..3dce4552b1 100644 --- a/pkg/interface/src/logic/lib/util.ts +++ b/pkg/interface/src/logic/lib/util.ts @@ -10,6 +10,7 @@ import { State, UseStore } from 'zustand'; import { Cage } from '~/types/cage'; import { BaseState } from '../state/base'; import anyAscii from 'any-ascii'; +import {Workspace} from '~/types'; enableMapSet(); @@ -46,6 +47,15 @@ export function parentPath(path: string) { return _.dropRight(path.split('/'), 1).join('/'); } +export function getResourcePath(workspace: Workspace, path: string, joined: boolean, mod: string) { + const base = workspace.type === 'group' + ? `/~landscape${workspace.group}` + : workspace.type === 'home' + ? `/~landscape/home` + : `/~landscape/messages`; + return `${base}/${joined ? 'resource' : 'join'}/${mod}${path}` +} + const DA_UNIX_EPOCH = bigInt('170141184475152167957503069145530368000'); // `@ud` ~1970.1.1 const DA_SECOND = bigInt('18446744073709551616'); // `@ud` ~s1 export function daToUnix(da: BigInteger) { @@ -102,6 +112,13 @@ export function clamp(x: number, min: number, max: number) { return Math.max(min, Math.min(max, x)); } +/** + * Euclidean modulo + */ +export function modulo(x: number, mod: number) { + return x < 0 ? (x % mod + mod) % mod : x % mod; +} + // color is a #000000 color export function adjustHex(color: string, amount: number): string { return f.flow( @@ -256,6 +273,8 @@ export function lengthOrder(a: string, b: string) { return b.length - a.length; } +export const keys = (o: T) => Object.keys(o) as (keyof T)[]; + // TODO: deprecated export function alphabetiseAssociations(associations: any) { const result = {}; diff --git a/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx b/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx index f3138ab398..880acf9e77 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx @@ -1,11 +1,14 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement, useCallback, useEffect } from 'react'; import { Associations, AppAssociations, Groups, Rolodex } from '@urbit/api'; -import { alphabeticalOrder } from '~/logic/lib/util'; +import { alphabeticalOrder, getResourcePath, modulo } from '~/logic/lib/util'; import { SidebarAppConfigs, SidebarListConfig, SidebarSort } from './types'; import { SidebarItem } from './SidebarItem'; import { Workspace } from '~/types/workspace'; import useMetadataState from '~/logic/state/metadata'; +import useGraphState from '~/logic/state/graph'; +import {useHistory} from 'react-router'; +import {useShortcut} from '~/logic/lib/shortcutContext'; function sidebarSort( associations: AppAssociations, @@ -48,6 +51,7 @@ export function SidebarList(props: { }): ReactElement { const { selected, group, config, workspace } = props; const associationState = useMetadataState(state => state.associations); + const graphKeys = useGraphState(s => s.graphKeys); const associations = { ...associationState.graph }; const ordered = Object.keys(associations) @@ -73,6 +77,30 @@ export function SidebarList(props: { }) .sort(sidebarSort(associations, props.apps)[config.sortBy]); + const history = useHistory(); + + const cycleChannels = useCallback((backward: boolean) => { + const idx = ordered.findIndex(s => s === selected); + const offset = backward ? -1 : 1 + + const newIdx = modulo(idx+offset, ordered.length - 1); + const { metadata, resource } = associations[ordered[newIdx]]; + const joined = graphKeys.has(resource.slice(7)); + const path = getResourcePath(workspace, resource, joined, metadata.config.graph) + history.push(path) + }, [selected, history.push]); + + useShortcut('ctrl+n', useCallback((e: KeyboardEvent) => { + cycleChannels(false); + e.preventDefault(); + }, [cycleChannels])); + + useShortcut('ctrl+p', useCallback((e: KeyboardEvent) => { + cycleChannels(true); + e.preventDefault(); + }, [cycleChannels])) + + return ( <> {ordered.map((path) => { From 880cde81cac5c462596f66d6d5fc8f78a6590844 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 14:03:10 +1000 Subject: [PATCH 08/11] settings: add shortcut config --- pkg/interface/src/logic/state/settings.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/interface/src/logic/state/settings.ts b/pkg/interface/src/logic/state/settings.ts index 767617a624..18194bcc24 100644 --- a/pkg/interface/src/logic/state/settings.ts +++ b/pkg/interface/src/logic/state/settings.ts @@ -17,6 +17,13 @@ export interface SettingsState extends BaseState { hideGroups: boolean; hideUtilities: boolean; }; + keyboard: { + cycleForward: string; + cycleBack: string; + navForward: string; + navBack: string; + hideSidebar: string; + } remoteContentPolicy: RemoteContentPolicy; leap: { categories: LeapCategories[]; @@ -60,6 +67,13 @@ const useSettingsState = createState('Settings', { tutorial: { seen: true, joined: undefined + }, + keyboard: { + cycleForward: 'ctrl+n', + cycleBack: 'ctrl+p', + navForward: 'ctrl+f', + navBack: 'ctrl+b', + hideSidebar: 'ctrl+h' } }); From 6d403b67fdd08986e3d0480d7052600727c70df2 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 14:27:51 +1000 Subject: [PATCH 09/11] settings: customize shortcuts --- pkg/interface/src/logic/lib/util.ts | 11 ++ pkg/interface/src/logic/state/settings.ts | 16 +-- .../components/lib/ShortcutSettings.tsx | 117 ++++++++++++++++++ .../src/views/apps/settings/settings.tsx | 3 + 4 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 pkg/interface/src/views/apps/settings/components/lib/ShortcutSettings.tsx diff --git a/pkg/interface/src/logic/lib/util.ts b/pkg/interface/src/logic/lib/util.ts index 3dce4552b1..4056aa8253 100644 --- a/pkg/interface/src/logic/lib/util.ts +++ b/pkg/interface/src/logic/lib/util.ts @@ -47,6 +47,17 @@ export function parentPath(path: string) { return _.dropRight(path.split('/'), 1).join('/'); } +export const getChord = (e: KeyboardEvent) => { + let chord = [e.key]; + if(e.metaKey) { + chord.unshift('meta'); + } + if(e.ctrlKey) { + chord.unshift('ctrl'); + } + return chord.join('+'); +} + export function getResourcePath(workspace: Workspace, path: string, joined: boolean, mod: string) { const base = workspace.type === 'group' ? `/~landscape${workspace.group}` diff --git a/pkg/interface/src/logic/state/settings.ts b/pkg/interface/src/logic/state/settings.ts index 18194bcc24..e1b9778f3e 100644 --- a/pkg/interface/src/logic/state/settings.ts +++ b/pkg/interface/src/logic/state/settings.ts @@ -2,6 +2,14 @@ import f from 'lodash/fp'; import { RemoteContentPolicy, LeapCategories, leapCategories } from "~/types/local-update"; import { BaseState, createState } from '~/logic/state/base'; +export interface ShortcutMapping { + cycleForward: string; + cycleBack: string; + navForward: string; + navBack: string; + hideSidebar: string; +} + export interface SettingsState extends BaseState { display: { @@ -17,13 +25,7 @@ export interface SettingsState extends BaseState { hideGroups: boolean; hideUtilities: boolean; }; - keyboard: { - cycleForward: string; - cycleBack: string; - navForward: string; - navBack: string; - hideSidebar: string; - } + keyboard: ShortcutMapping; remoteContentPolicy: RemoteContentPolicy; leap: { categories: LeapCategories[]; diff --git a/pkg/interface/src/views/apps/settings/components/lib/ShortcutSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/ShortcutSettings.tsx new file mode 100644 index 0000000000..e5dca84799 --- /dev/null +++ b/pkg/interface/src/views/apps/settings/components/lib/ShortcutSettings.tsx @@ -0,0 +1,117 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import _ from 'lodash'; + +import { Box, Col, Text } from '@tlon/indigo-react'; +import { Formik, Form, useField } from 'formik'; + +import GlobalApi from '~/logic/api/global'; +import { getChord } from '~/logic/lib/util'; +import useSettingsState, { + selectSettingsState, + ShortcutMapping, +} from '~/logic/state/settings'; +import { AsyncButton } from '~/views/components/AsyncButton'; +import { BackButton } from './BackButton'; + +interface ShortcutSettingsProps { + api: GlobalApi; +} + +const settingsSel = selectSettingsState(['keyboard']); + +export function ChordInput(props: { id: string; label: string }) { + const { id, label } = props; + const [capturing, setCapturing] = useState(false); + const [{ value }, , { setValue }] = useField(id); + const onCapture = useCallback(() => { + setCapturing(true); + }, []); + useEffect(() => { + if (!capturing) { + return; + } + function onKeydown(e: KeyboardEvent) { + if (['Control', 'Shift', 'Meta'].includes(e.key)) { + return; + } + const chord = getChord(e); + setValue(chord); + e.stopImmediatePropagation(); + e.preventDefault(); + setCapturing(false); + } + document.addEventListener('keydown', onKeydown); + return () => { + document.removeEventListener('keydown', onKeydown); + }; + }, [capturing]); + + return ( + <> + + {label} + + + {capturing ? 'Press' : value} + + + ); +} + +export default function ShortcutSettings(props: ShortcutSettingsProps) { + const { api } = props; + + const { keyboard } = useSettingsState(settingsSel); + + return ( + { + const promises = _.map(values, (value, key) => { + return keyboard[key] !== value + ? api.settings.putEntry('keyboard', key, value) + : Promise.resolve(); + }); + await Promise.all(promises); + actions.setStatus({ success: null }); + }} + > +
+ + + + + Shortcuts + + Customize keyboard shortcuts for landscape + + + + + + + + + Save Changes + + +
+ ); +} diff --git a/pkg/interface/src/views/apps/settings/settings.tsx b/pkg/interface/src/views/apps/settings/settings.tsx index 03f3f18a9e..cde3aa09cd 100644 --- a/pkg/interface/src/views/apps/settings/settings.tsx +++ b/pkg/interface/src/views/apps/settings/settings.tsx @@ -15,6 +15,7 @@ import { SidebarItem as BaseSidebarItem } from '~/views/landscape/components/Sid import { PropFunc } from '~/types'; import DebugPane from './components/lib/Debug'; import useHarkState from '~/logic/state/hark'; +import ShortcutSettings from './components/lib/ShortcutSettings'; export const Skeleton = (props: { children: ReactNode }) => ( @@ -115,6 +116,7 @@ export default function SettingsScreen(props: any) { + )} {hash === 'display' && } + {hash === 'shortcuts' && } {hash === 's3' && } {hash === 'leap' && } {hash === 'calm' && } From 5228cbccafeba34814410ce707b1f24c29e34e8c Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 5 May 2021 14:38:18 +1000 Subject: [PATCH 10/11] interface: add shortcut customisation --- pkg/interface/src/logic/lib/shortcutContext.tsx | 17 ++++++----------- pkg/interface/src/logic/state/settings.ts | 7 +++++++ .../src/views/landscape/components/Content.js | 9 ++++----- .../components/Sidebar/SidebarList.tsx | 6 +++--- .../src/views/landscape/components/Skeleton.tsx | 4 ++-- pkg/interface/src/wdyr.js | 4 ++-- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/pkg/interface/src/logic/lib/shortcutContext.tsx b/pkg/interface/src/logic/lib/shortcutContext.tsx index 2108a300c3..0f6dffb09b 100644 --- a/pkg/interface/src/logic/lib/shortcutContext.tsx +++ b/pkg/interface/src/logic/lib/shortcutContext.tsx @@ -3,26 +3,19 @@ import React, { useCallback, useContext, useEffect, + useMemo, useRef, useState, } from 'react'; import _ from 'lodash'; +import { getChord } from '~/logic/lib/util'; type Handler = (e: KeyboardEvent) => void; const fallback: ShortcutContextProps = { add: () => {}, remove: () => {}, }; -const getChord = (e: KeyboardEvent) => { - let chord = [e.key]; - if(e.metaKey) { - chord.unshift('meta'); - } - if(e.ctrlKey) { - chord.unshift('ctrl'); - } - return chord.join('+'); -} + export const ShortcutContext = createContext(fallback); export interface ShortcutContextProps { @@ -58,8 +51,10 @@ export function ShortcutContextProvider({ children }) { }; }, [shortcuts]); + const value = useMemo(() => ({ add, remove }), [add, remove]) + return ( - + {children} ); diff --git a/pkg/interface/src/logic/state/settings.ts b/pkg/interface/src/logic/state/settings.ts index e1b9778f3e..4fc032883b 100644 --- a/pkg/interface/src/logic/state/settings.ts +++ b/pkg/interface/src/logic/state/settings.ts @@ -1,6 +1,8 @@ import f from 'lodash/fp'; import { RemoteContentPolicy, LeapCategories, leapCategories } from "~/types/local-update"; +import { useShortcut as usePlainShortcut } from '~/logic/lib/shortcutContext'; import { BaseState, createState } from '~/logic/state/base'; +import {useCallback} from 'react'; export interface ShortcutMapping { cycleForward: string; @@ -79,4 +81,9 @@ const useSettingsState = createState('Settings', { } }); +export function useShortcut(name: T, cb: (e: KeyboardEvent) => void) { + const key = useSettingsState(useCallback(s => s.keyboard[name], [name])); + return usePlainShortcut(key, cb); +} + export default useSettingsState; diff --git a/pkg/interface/src/views/landscape/components/Content.js b/pkg/interface/src/views/landscape/components/Content.js index 21431df2ef..37ebe669c7 100644 --- a/pkg/interface/src/views/landscape/components/Content.js +++ b/pkg/interface/src/views/landscape/components/Content.js @@ -14,8 +14,7 @@ import GraphApp from '../../apps/graph/app'; import { PermalinkRoutes } from '~/views/apps/permalinks/app'; import { useLocalStorageState } from '~/logic/lib/useLocalStorageState'; -import { useShortcuts } from '~/logic/lib/useShortcuts'; -import { useShortcut } from '~/logic/lib/shortcutContext'; +import { useShortcut } from '~/logic/state/settings'; export const Container = styled(Box)` @@ -29,13 +28,13 @@ export const Container = styled(Box)` export const Content = (props) => { const history = useHistory(); - useShortcut('ctrl+f', useCallback((e) => { + useShortcut('navForward', useCallback((e) => { e.preventDefault(); e.stopImmediatePropagation(); history.goForward(); - }, [history.goBack])); + }, [history.goForward])); - useShortcut('ctrl+b', useCallback((e) => { + useShortcut('navBack', useCallback((e) => { e.preventDefault(); e.stopImmediatePropagation(); history.goBack(); diff --git a/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx b/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx index 880acf9e77..91afd70788 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/SidebarList.tsx @@ -8,7 +8,7 @@ import { Workspace } from '~/types/workspace'; import useMetadataState from '~/logic/state/metadata'; import useGraphState from '~/logic/state/graph'; import {useHistory} from 'react-router'; -import {useShortcut} from '~/logic/lib/shortcutContext'; +import { useShortcut } from '~/logic/state/settings'; function sidebarSort( associations: AppAssociations, @@ -90,12 +90,12 @@ export function SidebarList(props: { history.push(path) }, [selected, history.push]); - useShortcut('ctrl+n', useCallback((e: KeyboardEvent) => { + useShortcut('cycleForward', useCallback((e: KeyboardEvent) => { cycleChannels(false); e.preventDefault(); }, [cycleChannels])); - useShortcut('ctrl+p', useCallback((e: KeyboardEvent) => { + useShortcut('cycleBack', useCallback((e: KeyboardEvent) => { cycleChannels(true); e.preventDefault(); }, [cycleChannels])) diff --git a/pkg/interface/src/views/landscape/components/Skeleton.tsx b/pkg/interface/src/views/landscape/components/Skeleton.tsx index dddd8dfdc6..a5c3ba9e58 100644 --- a/pkg/interface/src/views/landscape/components/Skeleton.tsx +++ b/pkg/interface/src/views/landscape/components/Skeleton.tsx @@ -11,7 +11,7 @@ import { Workspace } from '~/types/workspace'; import useGraphState from '~/logic/state/graph'; import useHarkState from '~/logic/state/hark'; import ErrorBoundary from '~/views/components/ErrorBoundary'; -import {useShortcut} from '~/logic/lib/shortcutContext'; +import { useShortcut } from '~/logic/state/settings'; interface SkeletonProps { children: ReactNode; @@ -26,7 +26,7 @@ interface SkeletonProps { export function Skeleton(props: SkeletonProps): ReactElement { const [sidebar, setSidebar] = useState(true) - useShortcut('ctrl+h', useCallback(() => { + useShortcut('hideSidebar', useCallback(() => { setSidebar(s => !s); }, [])); const graphs = useGraphState(state => state.graphs); diff --git a/pkg/interface/src/wdyr.js b/pkg/interface/src/wdyr.js index db32b5d197..f4e2238761 100644 --- a/pkg/interface/src/wdyr.js +++ b/pkg/interface/src/wdyr.js @@ -1,8 +1,8 @@ import React from 'react'; -if (process.env.NODE_ENV === 'development') { +if (false && process.env.NODE_ENV === 'development') { const whyDidYouRender = require('@welldone-software/why-did-you-render'); whyDidYouRender(React, { trackAllPureComponents: true, }); -} \ No newline at end of file +} From 81f3d1ef9569e38bf042c055880ee688195d312c Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 10 May 2021 15:55:02 +1000 Subject: [PATCH 11/11] interface: update shortcut defaults --- pkg/interface/src/logic/state/settings.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/interface/src/logic/state/settings.ts b/pkg/interface/src/logic/state/settings.ts index 8c140df900..4f1c441125 100644 --- a/pkg/interface/src/logic/state/settings.ts +++ b/pkg/interface/src/logic/state/settings.ts @@ -73,11 +73,11 @@ const useSettingsState = createState('Settings', { joined: undefined }, keyboard: { - cycleForward: 'ctrl+n', - cycleBack: 'ctrl+p', - navForward: 'ctrl+f', - navBack: 'ctrl+b', - hideSidebar: 'ctrl+h' + cycleForward: 'ctrl+\'', + cycleBack: 'ctrl+;', + navForward: 'ctrl+[', + navBack: 'ctrl+[', + hideSidebar: 'ctrl+\\' } });