Skip to content

Commit 105d43f

Browse files
authored
feat: keybinding (#176)
* feat: add keybinding * feat: prevent default if key binding is matched
1 parent 50f05fc commit 105d43f

File tree

13 files changed

+449
-66
lines changed

13 files changed

+449
-66
lines changed

src/renderer/App.tsx

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { DeviceProvider } from './contexts/DeviceProvider';
2020
import Layout from './components/Layout';
2121
import AuthProvider from './contexts/AuthProvider';
2222
import 'react-toastify/dist/ReactToastify.css';
23+
import SettingScreen from './screens/SettingScreen/SettingScreen';
24+
import { KeyBindingProvider } from './contexts/KeyBindingProvider';
2325

2426
const ConnectionContext = createContext<{
2527
connect: (connectionConfig: ConnectionStoreItem) => void;
@@ -57,40 +59,43 @@ export default function App() {
5759
return (
5860
<DeviceProvider>
5961
<AppFeatureContext>
60-
<ConnectionContext.Provider
61-
value={{ connect: connectCallback, disconnect: disconnectCallback }}
62-
>
63-
<AuthProvider>
64-
<ContextMenuProvider>
65-
<DialogProvider>
66-
<NativeMenuProvider>
67-
<SqlExecuteProvider>
68-
<div
69-
style={{
70-
width: '100vw',
71-
height: '100vh',
72-
paddingBottom: 26,
73-
}}
74-
>
75-
<Layout>
76-
<Layout.Grow>
77-
{config ? (
78-
<DatabaseScreen config={config} />
79-
) : (
80-
<HomeScreen />
81-
)}
82-
</Layout.Grow>
83-
</Layout>
84-
<Layout.Fixed>
85-
<StatusBar />
86-
</Layout.Fixed>
87-
</div>
88-
</SqlExecuteProvider>
89-
</NativeMenuProvider>
90-
</DialogProvider>
91-
</ContextMenuProvider>
92-
</AuthProvider>
93-
</ConnectionContext.Provider>
62+
<KeyBindingProvider>
63+
<ConnectionContext.Provider
64+
value={{ connect: connectCallback, disconnect: disconnectCallback }}
65+
>
66+
<AuthProvider>
67+
<ContextMenuProvider>
68+
<DialogProvider>
69+
<NativeMenuProvider>
70+
<SqlExecuteProvider>
71+
<div
72+
style={{
73+
width: '100vw',
74+
height: '100vh',
75+
paddingBottom: 26,
76+
}}
77+
>
78+
<Layout>
79+
<Layout.Grow>
80+
{config ? (
81+
<DatabaseScreen config={config} />
82+
) : (
83+
<HomeScreen />
84+
)}
85+
</Layout.Grow>
86+
</Layout>
87+
<Layout.Fixed>
88+
<StatusBar />
89+
</Layout.Fixed>
90+
</div>
91+
<SettingScreen />
92+
</SqlExecuteProvider>
93+
</NativeMenuProvider>
94+
</DialogProvider>
95+
</ContextMenuProvider>
96+
</AuthProvider>
97+
</ConnectionContext.Provider>
98+
</KeyBindingProvider>
9499
</AppFeatureContext>
95100
</DeviceProvider>
96101
);

src/renderer/components/CodeEditor/SqlCodeEditor.tsx

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,18 @@ import { functionTooltip } from './functionTooltips';
2929
import { MySQLDialect, MySQLTooltips } from 'dialects/MySQLDialect';
3030
import { QueryDialetType } from 'libs/QueryBuilder';
3131
import { PgDialect, PgTooltips } from 'dialects/PgDialect copy';
32+
import { useKeybinding } from 'renderer/contexts/KeyBindingProvider';
3233

3334
const SqlCodeEditor = forwardRef(function SqlCodeEditor(
3435
props: ReactCodeMirrorProps & {
3536
schema?: DatabaseSchemas;
3637
currentDatabase?: string;
3738
dialect: QueryDialetType;
3839
},
39-
ref: Ref<ReactCodeMirrorRef>
40+
ref: Ref<ReactCodeMirrorRef>,
4041
) {
4142
const { schema, currentDatabase, ...codeMirrorProps } = props;
43+
const { binding } = useKeybinding();
4244
const theme = useCodeEditorTheme();
4345

4446
const customAutoComplete = useCallback(
@@ -47,16 +49,16 @@ const SqlCodeEditor = forwardRef(function SqlCodeEditor(
4749
context,
4850
tree,
4951
schema?.getSchema(),
50-
currentDatabase
52+
currentDatabase,
5153
);
5254
},
53-
[schema, currentDatabase]
55+
[schema, currentDatabase],
5456
);
5557

5658
const tableNameHighlightPlugin = useMemo(() => {
5759
if (schema && currentDatabase) {
5860
return createSQLTableNameHighlightPlugin(
59-
Object.keys(schema.getTableList(currentDatabase))
61+
Object.keys(schema.getTableList(currentDatabase)),
6062
);
6163
}
6264
return createSQLTableNameHighlightPlugin([]);
@@ -65,6 +67,44 @@ const SqlCodeEditor = forwardRef(function SqlCodeEditor(
6567
const dialect = props.dialect === 'mysql' ? MySQLDialect : PgDialect;
6668
const tooltips = props.dialect === 'mysql' ? MySQLTooltips : PgTooltips;
6769

70+
const keyExtension = useMemo(() => {
71+
return keymap.of([
72+
// Prevent the default behavior if it matches any of
73+
// these key binding. The reason is because the default
74+
// key binding for run is Ctrl + Enter. It is weird if
75+
// press Ctrl + Enter, will run and also insert newline
76+
// at the same time.
77+
...[
78+
binding['run-current-query'],
79+
binding['run-query'],
80+
binding['save-query'],
81+
].map((binding) => ({
82+
key: binding.toCodeMirrorKey(),
83+
preventDefault: true,
84+
run: () => true,
85+
})),
86+
{
87+
key: 'Tab',
88+
preventDefault: true,
89+
run: (target) => {
90+
if (completionStatus(target.state) === 'active') {
91+
acceptCompletion(target);
92+
} else {
93+
insertTab(target);
94+
}
95+
return true;
96+
},
97+
},
98+
{
99+
key: 'Ctrl-Space',
100+
mac: 'Cmd-i',
101+
preventDefault: true,
102+
run: startCompletion,
103+
},
104+
...defaultKeymap,
105+
]);
106+
}, [binding]);
107+
68108
return (
69109
<CodeMirror
70110
tabIndex={0}
@@ -78,27 +118,7 @@ const SqlCodeEditor = forwardRef(function SqlCodeEditor(
78118
drawSelection: false,
79119
}}
80120
extensions={[
81-
keymap.of([
82-
{
83-
key: 'Tab',
84-
preventDefault: true,
85-
run: (target) => {
86-
if (completionStatus(target.state) === 'active') {
87-
acceptCompletion(target);
88-
} else {
89-
insertTab(target);
90-
}
91-
return true;
92-
},
93-
},
94-
{
95-
key: 'Ctrl-Space',
96-
mac: 'Cmd-i',
97-
preventDefault: true,
98-
run: startCompletion,
99-
},
100-
...defaultKeymap,
101-
]),
121+
keyExtension,
102122
sql({
103123
dialect,
104124
}),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { PropsWithChildren } from 'react';
2+
import { createPortal } from 'react-dom';
3+
import styles from './styles.module.scss';
4+
import Icon from '../Icon';
5+
6+
interface OverlayProps {
7+
open?: boolean;
8+
onClose?: () => void;
9+
}
10+
11+
export default function OverlayScreen({
12+
children,
13+
open,
14+
onClose,
15+
}: PropsWithChildren<OverlayProps>) {
16+
return open
17+
? createPortal(
18+
<div className={styles.overlay}>
19+
<div className={styles.overlayBody}>{children}</div>
20+
<div className={styles.overlayHeader}>
21+
<div className={styles.overlayClose} onClick={onClose}>
22+
<Icon.Close />
23+
</div>
24+
</div>
25+
</div>,
26+
document.body,
27+
)
28+
: null;
29+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.overlay {
2+
position: fixed;
3+
z-index: 100;
4+
width: 100vw;
5+
height: 100vh;
6+
left: 0;
7+
top: 0;
8+
background: var(--color-surface);
9+
display: flex;
10+
justify-content: center;
11+
}
12+
13+
.overlayHeader {
14+
position: relative;
15+
}
16+
17+
.overlayClose {
18+
width: 30px;
19+
height: 30px;
20+
top: 30px;
21+
right: 30px;
22+
position: sticky;
23+
cursor: pointer;
24+
25+
img {
26+
width: 30px;
27+
height: 30px;
28+
}
29+
}
30+
31+
.overlayBody {
32+
max-width: 800px;
33+
flex-grow: 1;
34+
padding: 30px;
35+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { PropsWithChildren } from 'react';
2+
import styles from './styles.modules.scss';
3+
4+
export default function Table({ children }: PropsWithChildren) {
5+
return <table className={styles.table}>{children}</table>;
6+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.table {
2+
width: 100%;
3+
border-collapse: collapse;
4+
5+
td, th {
6+
text-align: left;
7+
padding: 5px 10px;
8+
border-bottom: 1px solid var(--color-border);
9+
}
10+
11+
tbody tr:hover {
12+
background: var(--color-surface-hover)
13+
}
14+
}

src/renderer/components/TextField/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface TextFieldCommonProps {
99
autoFocus?: boolean;
1010
placeholder?: string;
1111
onKeyDown?: (e: React.KeyboardEvent) => void;
12+
onBlur?: () => void;
1213
}
1314

1415
interface TextFieldProps extends TextFieldCommonProps {
@@ -30,6 +31,7 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
3031
actionClick,
3132
readOnly,
3233
onKeyDown,
34+
onBlur,
3335
},
3436
ref,
3537
) {
@@ -42,6 +44,7 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
4244
ref={ref}
4345
readOnly={readOnly}
4446
onKeyDown={onKeyDown}
47+
onBlur={onBlur}
4548
placeholder={placeholder}
4649
type={type || 'text'}
4750
autoFocus={autoFocus}

0 commit comments

Comments
 (0)