Skip to content

Commit

Permalink
Merge pull request #460 from subin-chella/keyboard-shortcut-with-library
Browse files Browse the repository at this point in the history
Keyboard shortcut on renderer process
  • Loading branch information
joseplayero authored Oct 18, 2024
2 parents 393805d + 06cb84e commit cc5b0ba
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 20 deletions.
1 change: 0 additions & 1 deletion electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ if (!app.requestSingleInstanceLock()) {
const preload = join(__dirname, '../preload/index.js')
const url = process.env.VITE_DEV_SERVER_URL
const indexHtml = join(process.env.DIST, 'index.html')

app.whenReady().then(async () => {
await ollamaService.init()
windowsManager.createWindow(store, preload, url, indexHtml)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 19 additions & 3 deletions src/components/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useCallback, useEffect, useState } from 'react'

import '../styles/global.css'
import ChatComponent from './Chat'
Expand All @@ -15,13 +15,25 @@ import { ChatProvider, useChatContext } from '@/contexts/ChatContext'
import { FileProvider, useFileContext } from '@/contexts/FileContext'
import ModalProvider from '@/contexts/ModalContext'
import CommonModals from './Common/CommonModals'
import useAppShortcuts from './shortcuts/use-shortcut'

const MainPageContent: React.FC = () => {
const [showSimilarFiles, setShowSimilarFiles] = useState(false)

const [isNewDirectoryModalOpen, setIsNewDirectoryModalOpen] = useState(false)
const { currentlyOpenFilePath } = useFileContext()

const { showChatbot } = useChatContext()
const { getShortcutDescription } = useAppShortcuts()
const openNewDirectoryModal = useCallback(() => {
setIsNewDirectoryModalOpen(true)
}, [])

useEffect(() => {
window.addEventListener('open-new-directory-modal', openNewDirectoryModal)
return () => {
window.removeEventListener('open-new-directory-modal', openNewDirectoryModal)
}
}, [openNewDirectoryModal])

return (
<div className="relative overflow-x-hidden">
Expand All @@ -33,7 +45,11 @@ const MainPageContent: React.FC = () => {
/>
<div className="flex h-below-titlebar">
<div className="border-y-0 border-l-0 border-r-[0.001px] border-solid border-neutral-700 pt-2.5">
<IconsSidebar />
<IconsSidebar
getShortcutDescription={getShortcutDescription}
isNewDirectoryModalOpen={isNewDirectoryModalOpen}
setIsNewDirectoryModalOpen={setIsNewDirectoryModalOpen}
/>
</div>

<ResizableComponent resizeSide="right">
Expand Down
55 changes: 41 additions & 14 deletions src/components/Sidebars/IconsSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,21 @@ import { useChatContext } from '@/contexts/ChatContext'
import { useContentContext } from '@/contexts/ContentContext'
import NewDirectoryComponent from '../File/NewDirectory'

const IconsSidebar: React.FC = () => {
export interface IconsSidebarProps {
readonly getShortcutDescription: (action: string) => string

readonly isNewDirectoryModalOpen: boolean

readonly setIsNewDirectoryModalOpen: React.Dispatch<React.SetStateAction<boolean>>
}

const IconsSidebar: React.FC<IconsSidebarProps> = ({
getShortcutDescription,
isNewDirectoryModalOpen,
setIsNewDirectoryModalOpen,
}) => {
const { sidebarShowing, setSidebarShowing } = useChatContext()
const [sidebarWidth, setSidebarWidth] = useState<number>(40)
const [isNewDirectoryModalOpen, setIsNewDirectoryModalOpen] = useState(false)

const { isSettingsModalOpen, setIsSettingsModalOpen, setIsFlashcardModeOpen } = useModalOpeners()
const { createUntitledNote } = useContentContext()
Expand Down Expand Up @@ -50,7 +61,7 @@ const IconsSidebar: React.FC = () => {
className="mx-auto text-gray-200"
color={sidebarShowing === 'files' ? 'white' : 'gray'}
size={18}
title="Files"
title={getShortcutDescription('open-files') || 'Open Files'}
/>
</div>
</div>
Expand All @@ -63,7 +74,7 @@ const IconsSidebar: React.FC = () => {
color={sidebarShowing === 'chats' ? 'white' : 'gray'}
className="cursor-pointer text-gray-100 "
size={18}
title={sidebarShowing === 'chats' ? 'Close Chatbot' : 'Open Chatbot'}
title={getShortcutDescription('open-chat-bot') || 'Open Chatbot'}
/>
</div>
</div>
Expand All @@ -76,7 +87,7 @@ const IconsSidebar: React.FC = () => {
color={sidebarShowing === 'search' ? 'white' : 'gray'}
size={18}
className="text-gray-200"
title="Semantic Search"
title={getShortcutDescription('open-search') || 'Semantic Search'}
/>
</div>
</div>
Expand All @@ -85,23 +96,38 @@ const IconsSidebar: React.FC = () => {
onClick={() => createUntitledNote()}
>
<div className="flex size-4/5 items-center justify-center rounded hover:bg-neutral-700">
<HiOutlinePencilAlt className="text-gray-200" color="gray" size={22} title="New Note" />
<HiOutlinePencilAlt
className="text-gray-200"
color="gray"
size={22}
title={getShortcutDescription('open-new-note') || 'New Note'}
/>
</div>
</div>
<div
className="mt-[2px] flex h-8 w-full cursor-pointer items-center justify-center border-none bg-transparent "
onClick={() => setIsNewDirectoryModalOpen(true)}
>
<div className="flex size-4/5 items-center justify-center rounded hover:bg-neutral-700">
<VscNewFolder className="text-gray-200" color="gray" size={18} title="New Directory" />
<VscNewFolder
className="text-gray-200"
color="gray"
size={18}
title={getShortcutDescription('open-new-directory-modal') || 'New Directory'}
/>
</div>
</div>
<div
className="flex h-8 w-full cursor-pointer items-center justify-center border-none bg-transparent "
onClick={() => setIsFlashcardModeOpen(true)}
>
<div className="flex size-4/5 items-center justify-center rounded hover:bg-neutral-700">
<MdOutlineQuiz className="text-gray-200" color="gray" size={19} title="Flashcard quiz" />
<MdOutlineQuiz
className="text-gray-200"
color="gray"
size={19}
title={getShortcutDescription('open-flashcard-quiz-modal') || 'Flashcard quiz'}
/>
</div>
</div>

Expand All @@ -118,13 +144,14 @@ const IconsSidebar: React.FC = () => {
type="button"
aria-label="Open Settings"
>
<MdSettings color="gray" size={18} className="mb-3 size-6 text-gray-100" title="Settings" />
<MdSettings
color="gray"
size={18}
className="mb-3 size-6 text-gray-100"
title={getShortcutDescription('open-settings-modal') || 'Settings'}
/>
</button>
<NewDirectoryComponent
isOpen={isNewDirectoryModalOpen}
onClose={() => setIsNewDirectoryModalOpen(false)}
// parentDirectoryPath={parentDirectoryPathForNewDirectory}
/>
<NewDirectoryComponent isOpen={isNewDirectoryModalOpen} onClose={() => setIsNewDirectoryModalOpen(false)} />
</div>
)
}
Expand Down
54 changes: 54 additions & 0 deletions src/components/shortcuts/shortcutDefinitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export interface Shortcut {
key: string
action: string
description: string
displayValue: {
mac: string
other: string
}
}

export const shortcuts: Shortcut[] = [
{
key: 'mod+O',
action: 'open-files',
description: 'Open Files',
displayValue: { mac: 'Cmd+O', other: 'Ctrl+O' },
},
{
key: 'mod+N',
action: 'open-new-note',
description: 'New Note',
displayValue: { mac: 'Cmd+N', other: 'Ctrl+N' },
},
{
key: 'mod+P',
action: 'open-search',
description: 'Semantic Search',
displayValue: { mac: 'Cmd+P', other: 'Ctrl+P' },
},
{
key: 'mod+T',
action: 'open-chat-bot',
description: 'Open Chatbot',
displayValue: { mac: 'Cmd+T', other: 'Ctrl+T' },
},
{
key: 'mod+D',
action: 'open-new-directory-modal',
description: 'New Directory',
displayValue: { mac: 'Cmd+D', other: 'Ctrl+D' },
},
{
key: 'mod+L',
action: 'open-flashcard-quiz-modal',
description: 'Flashcard quiz',
displayValue: { mac: 'Cmd+L', other: 'Ctrl+L' },
},
{
key: 'mod+,',
action: 'open-settings-modal',
description: 'Settings',
displayValue: { mac: 'Cmd+,', other: 'Ctrl+,' },
},
]
81 changes: 81 additions & 0 deletions src/components/shortcuts/use-shortcut.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useCallback, useEffect, useRef } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { useModalOpeners } from '../../contexts/ModalContext'
import { useChatContext } from '../../contexts/ChatContext'
import { useContentContext } from '@/contexts/ContentContext'
import { shortcuts } from './shortcutDefinitions'

function useAppShortcuts() {
const { setIsFlashcardModeOpen, setIsSettingsModalOpen } = useModalOpeners()
const { setSidebarShowing, openNewChat } = useChatContext()
const { createUntitledNote } = useContentContext()

const handleShortcut = useCallback(
(action: string) => {
switch (action) {
case 'open-new-note':
createUntitledNote()
break
case 'open-new-directory-modal':
window.dispatchEvent(new CustomEvent('open-new-directory-modal'))
break
case 'open-search':
setSidebarShowing('search')
break
case 'open-files':
setSidebarShowing('files')
break
case 'open-chat-bot':
openNewChat()
break
case 'open-flashcard-quiz-modal':
setIsFlashcardModeOpen(true)
break
case 'open-settings-modal':
setIsSettingsModalOpen(true)
break
default:
// No other cases
break
}
},
[createUntitledNote, setSidebarShowing, setIsFlashcardModeOpen, setIsSettingsModalOpen, openNewChat],
)

const handleShortcutRef = useRef(handleShortcut)
handleShortcutRef.current = handleShortcut

const debouncedHandleKeyDown = useDebouncedCallback((event: KeyboardEvent) => {
const modifierPressed = event.ctrlKey || event.metaKey
const keyPressed = event.key.toLowerCase()

const triggeredShortcut = shortcuts.find((s) => {
const [mod, key] = s.key.toLowerCase().split('+')
return mod === 'mod' && modifierPressed && key === keyPressed
})

if (triggeredShortcut) {
event.preventDefault()
handleShortcutRef.current(triggeredShortcut.action)
}
}, 100)

useEffect(() => {
window.addEventListener('keydown', debouncedHandleKeyDown)
return () => {
window.removeEventListener('keydown', debouncedHandleKeyDown)
debouncedHandleKeyDown.cancel()
}
}, [debouncedHandleKeyDown])

const getShortcutDescription = useCallback((action: string) => {
const shortcut = shortcuts.find((s) => s.action === action)
if (!shortcut) return ''
const platform = navigator.platform.toLowerCase().includes('mac') ? 'mac' : 'other'
return `${shortcut.description} (${shortcut.displayValue[platform]})`
}, [])

return { getShortcutDescription }
}

export default useAppShortcuts

0 comments on commit cc5b0ba

Please sign in to comment.