Skip to content

Commit 13a9597

Browse files
committed
feat: keyboard table
1 parent 03f9da2 commit 13a9597

File tree

14 files changed

+282
-14
lines changed

14 files changed

+282
-14
lines changed

apps/linebyline/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"react-router-dom": "^6.9.0",
3434
"remixicon": "^3.4.0",
3535
"styled-components": "^5.3.11",
36+
"tinykeys": "^2.1.0",
3637
"zustand": "^4.3.9"
3738
},
3839
"devDependencies": {

apps/linebyline/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import { Route, Routes } from 'react-router-dom'
88
import 'remixicon/fonts/remixicon.css'
99
import { ThemeProvider } from 'styled-components'
1010
import { GlobalStyles } from './globalStyles'
11-
import { useGlobalSettingData, useGlobalTheme } from './hooks'
12-
import useGlobalOSInfo from './hooks/useOSInfo'
11+
import { useGlobalSettingData, useGlobalTheme, useGlobalKeyboard, useGlobalOSInfo } from './hooks'
1312
import { i18nInit } from './i18n'
1413
import { Root, Setting } from '@/router'
1514
import { loadTask, use } from '@/helper/schedule'
@@ -20,6 +19,7 @@ import { getFileObject, getFileObjectByPath } from './helper/files'
2019

2120
function App() {
2221
useGlobalOSInfo()
22+
useGlobalKeyboard()
2323
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2424
const [_, handler] = useGlobalSettingData()
2525
const { themeColors, muiTheme, setTheme } = useGlobalTheme()

apps/linebyline/src/helper/cacheManager/settingMap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const settingMap = {
2525
},
2626
},
2727
},
28+
keyboard: {}
2829
}
2930

3031
export enum SettingKeys {

apps/linebyline/src/hooks/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export { default as useGlobalCacheData } from './useCacheData'
22
export { default as useGlobalSettingData } from './useSettingData'
33
export { default as useGlobalTheme } from './useTheme'
4+
export { default as useGlobalKeyboard } from './useKeyboard'
5+
export { default as useGlobalOSInfo } from './useOSInfo'
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { invoke } from '@tauri-apps/api'
2+
import { createGlobalStore } from 'hox'
3+
import { useEffect, useState } from 'react'
4+
import type { KeyBindingMap } from 'tinykeys'
5+
import { createKeybindingsHandler } from 'tinykeys'
6+
import { useCommandStore } from '@/stores'
7+
8+
const getTinyKeyBinding = (keyMap: string[]) => {
9+
let keyBinding = ''
10+
keyMap.forEach((key, index) => {
11+
if (key === 'mod') {
12+
keyBinding += '$mod'
13+
} else {
14+
keyBinding += key
15+
}
16+
17+
if (index < keyMap.length - 1) {
18+
keyBinding += '+'
19+
}
20+
})
21+
return keyBinding
22+
}
23+
24+
function useKeyboard() {
25+
const [keyboardInfos, setKeyboardInfos] = useState<KeyboardInfo[]>([])
26+
const { execute } = useCommandStore()
27+
28+
useEffect(() => {
29+
invoke<{ cmds: KeyboardInfo[] }>('get_keyboard_infos').then((res) => {
30+
setKeyboardInfos(res.cmds)
31+
})
32+
}, [])
33+
34+
useEffect(() => {
35+
const keybindingMap: KeyBindingMap = {}
36+
37+
keyboardInfos.forEach((keyboardInfo) => {
38+
if (keyboardInfo.key_map.length > 0) {
39+
const keybind = getTinyKeyBinding(keyboardInfo.key_map)
40+
keybindingMap[keybind] = () => {
41+
execute(keyboardInfo.id)
42+
}
43+
}
44+
})
45+
console.log('keybindingMap', keybindingMap)
46+
const handler = createKeybindingsHandler(keybindingMap)
47+
48+
window.addEventListener('keydown', handler)
49+
50+
return () => {
51+
window.removeEventListener('keydown', handler)
52+
}
53+
}, [execute, keyboardInfos])
54+
55+
return {
56+
keyboardInfos,
57+
}
58+
}
59+
60+
const [useGlobalKeyboard] = createGlobalStore(useKeyboard)
61+
62+
export default useGlobalKeyboard
63+
64+
interface KeyboardInfo {
65+
id: string
66+
desc: string
67+
key_map: string[]
68+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Table from '@mui/material/Table'
2+
import TableBody from '@mui/material/TableBody'
3+
import TableCell from '@mui/material/TableCell'
4+
import TableContainer from '@mui/material/TableContainer'
5+
import TableHead from '@mui/material/TableHead'
6+
import TableRow from '@mui/material/TableRow'
7+
import Paper from '@mui/material/Paper'
8+
import { useGlobalKeyboard, useGlobalTheme } from '@/hooks'
9+
10+
export function KeyboardTable() {
11+
const { themeColors } = useGlobalTheme()
12+
const { keyboardInfos } = useGlobalKeyboard()
13+
14+
return (
15+
<TableContainer component={Paper}>
16+
<Table size='small' aria-label='caption table'>
17+
<TableHead
18+
sx={{
19+
backgroundColor: themeColors.tipsBgColor,
20+
}}
21+
>
22+
<TableRow>
23+
<TableCell>Command</TableCell>
24+
<TableCell>Descibe</TableCell>
25+
<TableCell>Keybinding</TableCell>
26+
</TableRow>
27+
</TableHead>
28+
<TableBody>
29+
{keyboardInfos.map((row) => (
30+
<TableRow key={row.id}>
31+
<TableCell>{row.id}</TableCell>
32+
<TableCell>{row.desc}</TableCell>
33+
<TableCell>{row.key_map.join('+')}</TableCell>
34+
</TableRow>
35+
))}
36+
</TableBody>
37+
<caption>Currently, custom shortcut keys are not supported.</caption>
38+
</Table>
39+
</TableContainer>
40+
)
41+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './KeyboardTable'

apps/linebyline/src/router/Setting/index.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import settingMap from '@/helper/cacheManager/settingMap'
77
import Logo from '@/assets/logo.svg'
88
import { invoke } from '@tauri-apps/api'
99
import TitleBar from '@/components/TitleBar'
10+
import { KeyboardTable } from './KeyboardTable'
1011

1112
export interface DialogTitleProps {
1213
children?: ReactNode
@@ -34,6 +35,23 @@ function Setting() {
3435
})
3536
}, [])
3637

38+
const renderCurrentSettingData = () => {
39+
if (curGroupKey === 'keyboard') {
40+
return <KeyboardTable />
41+
}
42+
43+
return curGroupKeys.map((key) => {
44+
return (
45+
<SettingGroup
46+
key={key}
47+
group={curGroup[key]}
48+
groupKey={key}
49+
categoryKey={curGroupKey}
50+
/>
51+
)
52+
})
53+
}
54+
3755
return (
3856
<>
3957
<TitleBar transparent />
@@ -71,16 +89,7 @@ function Setting() {
7189
<div className="conf-path">
7290
<small>Path: {confPath}</small>
7391
</div>
74-
{curGroupKeys.map((key) => {
75-
return (
76-
<SettingGroup
77-
key={key}
78-
group={curGroup[key]}
79-
groupKey={key}
80-
categoryKey={curGroupKey}
81-
/>
82-
)
83-
})}
92+
{renderCurrentSettingData()}
8493
</div>
8594
</Container>
8695
</>

apps/linebyline/src/stores/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { default as useEditorStore } from './useEditorStore'
2+
export { default as useCommandStore } from './useCommandStore'
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { create } from 'zustand'
2+
3+
const useCommandStore = create<CommandStore>((set, get) => {
4+
return {
5+
commands: {},
6+
addCommand: ({ id, handler }) => {
7+
set((state) => {
8+
return {
9+
...state,
10+
commands: {
11+
...state.commands,
12+
[id]: {
13+
exec: handler,
14+
},
15+
},
16+
}
17+
})
18+
},
19+
execute: (id: string) => {
20+
const { commands } = get()
21+
const command = commands[id]
22+
if (command) {
23+
command.exec()
24+
} else {
25+
console.error(`command ${id} not found`)
26+
}
27+
},
28+
}
29+
})
30+
31+
interface CommandStore {
32+
commands: Commands
33+
addCommand: (command: { id: string; handler: () => any }) => void
34+
execute: (id: string) => void
35+
}
36+
37+
export default useCommandStore
38+
39+
type Commands = Record<string, Command>
40+
41+
interface Command {
42+
exec: () => void
43+
}

src-tauri/src/app/keyboard.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use super::conf;
2+
use crate::fc::{create_file, exists};
3+
use serde::{Deserialize, Serialize};
4+
use std::path::PathBuf;
5+
6+
#[derive(Serialize, Deserialize)]
7+
struct KeyboardInfo {
8+
id: String,
9+
desc: String,
10+
key_map: Vec<String>,
11+
}
12+
13+
impl KeyboardInfo {
14+
pub fn new(id: String, desc: String, key_map: Vec<String>) -> Self {
15+
Self { id, desc, key_map }
16+
}
17+
}
18+
19+
#[derive(Serialize, Deserialize)]
20+
pub struct KeyboardInfos {
21+
cmds: Vec<KeyboardInfo>,
22+
}
23+
24+
impl KeyboardInfos {
25+
pub fn new() -> Self {
26+
let key_map: Vec<String> = Vec::new();
27+
Self {
28+
cmds: vec![
29+
KeyboardInfo::new(
30+
"app:dialog_about".to_string(),
31+
"获取应用信息".to_string(),
32+
Vec::new(),
33+
),
34+
KeyboardInfo::new(
35+
"app:window_setting".to_string(),
36+
"打开设置窗口".to_string(),
37+
Vec::new(),
38+
),
39+
KeyboardInfo::new(
40+
"app:open_folder".to_string(),
41+
"打开文件夹".to_string(),
42+
vec!["mod".to_string(), "Shift".to_string(), "o".to_string()],
43+
)
44+
],
45+
}
46+
}
47+
48+
pub fn get_path() -> PathBuf {
49+
conf::app_root().join("keybord_map.json")
50+
}
51+
52+
pub fn read() -> Self {
53+
match std::fs::read_to_string(Self::get_path()) {
54+
Ok(v) => {
55+
if let Ok(v2) = serde_json::from_str::<KeyboardInfos>(&v) {
56+
v2
57+
} else {
58+
Self::default()
59+
}
60+
}
61+
Err(err) => Self::default(),
62+
}
63+
}
64+
65+
pub fn write(self) -> Self {
66+
let path = &Self::get_path();
67+
if !exists(path) {
68+
create_file(path).unwrap();
69+
}
70+
if let Ok(v) = serde_json::to_string_pretty(&self) {
71+
std::fs::write(path, v).unwrap_or_else(|err| {
72+
Self::default().write();
73+
});
74+
} else {
75+
}
76+
self
77+
}
78+
}
79+
80+
impl Default for KeyboardInfos {
81+
fn default() -> Self {
82+
Self::new()
83+
}
84+
}
85+
86+
pub mod cmd {
87+
use super::KeyboardInfos;
88+
use tauri::{command, AppHandle};
89+
90+
#[command]
91+
pub fn get_keyboard_infos() -> KeyboardInfos {
92+
KeyboardInfos::read()
93+
}
94+
}

src-tauri/src/app/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod conf;
2+
pub mod keyboard;

src-tauri/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod fc;
77
mod menu;
88
mod setup;
99

10-
use app::conf;
10+
use app::{conf, keyboard};
1111

1212
fn main() {
1313
let context = tauri::generate_context!();
@@ -21,7 +21,8 @@ fn main() {
2121
conf::cmd::get_app_conf,
2222
conf::cmd::reset_app_conf,
2323
conf::cmd::save_app_conf,
24-
conf::cmd::open_conf_window
24+
conf::cmd::open_conf_window,
25+
keyboard::cmd::get_keyboard_infos,
2526
])
2627
.setup(setup::init)
2728
.menu(menu::generate_menu())

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10153,6 +10153,11 @@ tinycolor2@^1.4.1:
1015310153
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
1015410154
integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==
1015510155

10156+
tinykeys@^2.1.0:
10157+
version "2.1.0"
10158+
resolved "https://registry.yarnpkg.com/tinykeys/-/tinykeys-2.1.0.tgz#1341563e92a7fac9ca90053fddaf2b7553500298"
10159+
integrity sha512-/MESnqBD1xItZJn5oGQ4OsNORQgJfPP96XSGoyu4eLpwpL0ifO0SYR5OD76u0YMhMXsqkb0UqvI9+yXTh4xv8Q==
10160+
1015610161
tinypool@^0.6.0:
1015710162
version "0.6.0"
1015810163
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.6.0.tgz#c3640b851940abe2168497bb6e43b49fafb3ba7b"

0 commit comments

Comments
 (0)