Skip to content

Commit

Permalink
feat: shortcuts
Browse files Browse the repository at this point in the history
  • Loading branch information
e-zz committed Apr 8, 2024
1 parent d302c7e commit d6b9ae6
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 5 deletions.
60 changes: 58 additions & 2 deletions src/Agenda3/components/MainArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { LeftOutlined, RightOutlined } from '@ant-design/icons'
import { Button, Segmented } from 'antd'
import dayjs from 'dayjs'
import { useAtom, useAtomValue } from 'jotai'
import { useRef, useState } from 'react'
import { useRef, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { FiSettings, FiXCircle } from 'react-icons/fi'
import { LuCalendarDays, LuKanbanSquare } from 'react-icons/lu'
Expand All @@ -15,7 +15,7 @@ import { cn } from '@/util/util'
import Filter from './Filter'
import UploadIcs from './UploadIcs'
import Calendar, { type CalendarHandle } from './calendar/Calendar'
import CalendarOperation, { type CalendarView } from './calendar/CalendarAdvancedOperation'
import CalendarOperation, { CALENDAR_VIEWS, type CalendarView } from './calendar/CalendarAdvancedOperation'
import KanBan, { type KanBanHandle } from './kanban/KanBan'
import SettingsModal from './modals/SettingsModal'

Expand Down Expand Up @@ -78,6 +78,62 @@ const MultipleView = ({ className }: { className?: string }) => {
}))
}

const set_calendar_view = (view: CalendarView) => {
calendarRef.current?.changeView(view as CalendarView)
setApp((_app) => ({ ..._app, calendarView: view }))
track('Calendar View Change', { calendarView: view })
}

const tog_calendar_view = () => {
const view =
app.calendarView === CALENDAR_VIEWS.dayGridMonth ? CALENDAR_VIEWS.timeGridWeek : CALENDAR_VIEWS.dayGridMonth
set_calendar_view(view)
}

useEffect(() => {
let lastKeyDownTime = 0
let lastKey = ''
const doubleClickThreshold = 500 // 500 milliseconds

function handleKeyDown(event) {
// Get the currently focused element
const activeElement = document.activeElement

// If the focused element is an input or textarea, ignore the keydown event
if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) return

const calendarApi = calendarRef.current

const currentTime = new Date().getTime()

// Handle double-tap [[ or ]]
if (currentTime - lastKeyDownTime <= doubleClickThreshold && lastKey === event.code) {
// [[ : go to the previous month
if (event.code === 'BracketLeft') calendarApi?.prev()
// ]] : go to the next month
if (event.code === 'BracketRight') calendarApi?.next()
}

// Handle other keystrokes
if (event.code === 'KeyW') tog_calendar_view()

if (event.code === 'KeyT') {
const view = app.view === 'calendar' ? 'tasks' : 'calendar'
onClickAppViewChange(view)
// TODO UI: toggle the state of Calendar-Tasks slider accordingly
}

lastKey = event.code
lastKeyDownTime = currentTime
}

document.addEventListener('keydown', handleKeyDown)

return () => {
document.removeEventListener('keydown', handleKeyDown)
}
}, [app.calendarView, app.view, setApp])

return (
<div className={cn('relative z-0 flex w-0 flex-1 flex-col py-1 pl-2', className)}>
{/* ========= View Actions ========= */}
Expand Down
12 changes: 11 additions & 1 deletion src/Agenda3/components/calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } f

import { genDurationString, updateBlockDateInfo } from '@/Agenda3/helpers/block'
import { transformAgendaTaskToCalendarEvent } from '@/Agenda3/helpers/fullCalendar'
import { navToLogseqBlock } from '@/Agenda3/helpers/logseq'
import { track } from '@/Agenda3/helpers/umami'
import useAgendaEntities from '@/Agenda3/hooks/useAgendaEntities'
import { appAtom } from '@/Agenda3/models/app'
import { tasksWithStartAtom } from '@/Agenda3/models/entities/tasks'
import { logseqAtom } from '@/Agenda3/models/logseq'
import { settingsAtom } from '@/Agenda3/models/settings'
import type { AgendaTaskWithStart } from '@/types/task'
import { cn } from '@/util/util'
Expand All @@ -41,6 +43,7 @@ const Calendar = ({ onCalendarTitleChange }: CalendarProps, ref) => {
const { updateEntity } = useAgendaEntities()
const tasksWithStart = useAtomValue(tasksWithStartAtom)
const settings = useAtomValue(settingsAtom)
const { currentGraph } = useAtomValue(logseqAtom)
const groupType = settings.selectedFilters?.length ? 'filter' : 'page'
const showTasks = tasksWithStart?.filter((task) =>
settings.viewOptions?.hideCompleted ? task.status === 'todo' : true,
Expand Down Expand Up @@ -78,6 +81,9 @@ const Calendar = ({ onCalendarTitleChange }: CalendarProps, ref) => {
task: info.event.extendedProps as AgendaTaskWithStart,
})
}
const onEventCtrlClick = (info: EventClickArg) => {
navToLogseqBlock(info.event.extendedProps as AgendaTaskWithStart, currentGraph)
}
const onEventScheduleUpdate = (info: EventResizeDoneArg | EventReceiveArg | EventDropArg) => {
// const calendarApi = calendarRef.current?.getApi()
const { start, end, id: blockUUID, allDay, extendedProps } = info.event
Expand Down Expand Up @@ -228,7 +234,11 @@ const Calendar = ({ onCalendarTitleChange }: CalendarProps, ref) => {
}}
// click event
eventClick={(info) => {
onEventClick(info)
if (info.jsEvent?.ctrlKey) {
onEventCtrlClick(info)
} else {
onEventClick(info)
}
track('Calendar: Click Event', { calendarView: info.view.type })
}}
select={(info) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IoMdCheckmark } from 'react-icons/io'
import useSettings from '@/Agenda3/hooks/useSettings'
import { cn } from '@/util/util'

const CALENDAR_VIEWS = {
export const CALENDAR_VIEWS = {
dayGridMonth: 'dayGridMonth',
timeGridWeek: 'timeGridWeek',
} as const
Expand Down
18 changes: 17 additions & 1 deletion src/Agenda3/components/kanban/taskCard/TaskCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { RiDeleteBin4Line } from 'react-icons/ri'
import { VscDebugConsole } from 'react-icons/vsc'

import { minutesToHHmm } from '@/Agenda3/helpers/fullCalendar'
import { navToLogseqBlock } from '@/Agenda3/helpers/logseq'
import useAgendaEntities from '@/Agenda3/hooks/useAgendaEntities'
import { logseqAtom } from '@/Agenda3/models/logseq'
import { settingsAtom } from '@/Agenda3/models/settings'
import { DEFAULT_ESTIMATED_TIME } from '@/constants/agenda'
import type { AgendaEntity } from '@/types/entity'
Expand All @@ -18,6 +20,7 @@ import TaskModal from '../../modals/TaskModal'
import Toolbar from './Toolbar'

const TaskCard = ({ task }: { task: AgendaTaskWithStart }) => {
const currentGraph = useAtomValue(logseqAtom).currentGraph
const settings = useAtomValue(settingsAtom)
const groupType = settings.selectedFilters?.length ? 'filter' : 'page'

Expand Down Expand Up @@ -45,6 +48,15 @@ const TaskCard = ({ task }: { task: AgendaTaskWithStart }) => {
const onRemoveDate = async (taskId: string) => {
updateEntity({ type: 'task-remove-date', id: taskId, data: null })
}
const onClickTask = (e: React.MouseEvent, task: AgendaTaskWithStart) => {
if (e.ctrlKey) {
navToLogseqBlock(task, currentGraph)
console.log(task)
} else {
setEditTaskModal({ open: true, task })
}
e.stopPropagation()
}

return (
<div
Expand Down Expand Up @@ -93,7 +105,11 @@ const TaskCard = ({ task }: { task: AgendaTaskWithStart }) => {
},
}}
>
<div onClick={() => setEditTaskModal({ open: true, task })}>
<div
onClick={(e) => {
onClickTask(e, task)
}}
>
{/* ========= Toolbar ========= */}
<Toolbar task={task} groupType={groupType} onClickMark={onClickTaskMark} />

Expand Down
37 changes: 37 additions & 0 deletions src/Agenda3/components/modals/TaskModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import TimeSelect from '@/components/TaskModal/components/TimeSelect'
import { SHOW_DATETIME_FORMATTER, SHOW_DATE_FORMATTER } from '@/constants/agenda'
import type { AgendaEntity } from '@/types/entity'
import type { AgendaTaskWithStart, TimeLog } from '@/types/task'
import { getOS } from '@/util/util'

import ObjectiveSelect from '../../forms/ObjectiveSelect'
import PageSelect from '../../forms/PageSelect'
Expand Down Expand Up @@ -151,6 +152,42 @@ const TaskModal = ({
onCancel?.()
setInternalOpen(false)
}
// Add keyboard event listener
useEffect(() => {
function handleKeyDown(event) {
const isMac = getOS() === 'mac'
const mainModifierKey = isMac ? event.metaKey : event.ctrlKey

if (event.code === 'KeyW' && mainModifierKey) {
// Close the modal on pressing ctrl+q (or cmd+q on Mac)
onCancel?.()
setInternalOpen(false)
event.stopPropagation()
} else if (event.code === 'KeyS' && mainModifierKey) {
// Save and close the modal on pressing ctrl+s (or cmd+s on Mac)
onOk?.()
setInternalOpen(false)
event.stopPropagation()
} else if (event.code === 'Enter' && mainModifierKey) {
// toggle TODO status on pressing ctrl+Enter (or cmd+Enter on Mac)
if (info.type === 'edit' && info.initialTaskData.status === 'done') {
onSwitchTaskStatus('todo')
}
if (info.type === 'edit' && info.initialTaskData.status === 'todo') {
onSwitchTaskStatus('done')
setInternalOpen(false)
}
event.stopPropagation()
}
}

window.addEventListener('keydown', handleKeyDown)

return () => {
window.removeEventListener('keydown', handleKeyDown)
}
}, [])

useEffect(() => {
// 增加延时,否则二次打开无法自动聚焦
if (_open) setTimeout(() => titleInputRef.current?.focus(), 0)
Expand Down

0 comments on commit d6b9ae6

Please sign in to comment.