Skip to content

Commit

Permalink
Add hotkeys for unslecting and deleting timespans (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
CalebBassham authored Oct 23, 2023
1 parent b707a27 commit 6842b5c
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 39 deletions.
41 changes: 5 additions & 36 deletions ui/src/timespan/TimeSpan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import {RemoveTimeSpan, RemoveTimeSpanVariables} from '../gql/__generated__/RemoveTimeSpan';
import {useStateAndDelegateWithDelayOnChange} from '../utils/hooks';
import {TimeSpans} from '../gql/__generated__/TimeSpans';
import {isSameDate} from '../utils/time';
import {Trackers} from '../gql/__generated__/Trackers';
import {addTimeSpanToCache, removeFromTrackersCache} from '../gql/utils';
import {StartTimer, StartTimerVariables} from '../gql/__generated__/StartTimer';
import {RelativeTime, RelativeToNow} from '../common/RelativeTime';
import ShowNotesIcon from '@material-ui/icons/KeyboardArrowDown';
import HideNotesIcon from '@material-ui/icons/KeyboardArrowUp';
import {removeTimeSpanOptions} from './mutations';

interface Range {
from: moment.Moment;
Expand Down Expand Up @@ -82,40 +81,10 @@ export const TimeSpan: React.FC<TimeSpanProps> = React.memo(
clearTimeout(note.current.handle);
return updateTimeSpan({variables: {...variables, note: note.current.value}});
};
const [removeTimeSpan] = useMutation<RemoveTimeSpan, RemoveTimeSpanVariables>(gqlTimeSpan.RemoveTimeSpan, {
update: (cache, {data}) => {
let oldData: TimeSpans | null = null;
try {
oldData = cache.readQuery<TimeSpans>({query: gqlTimeSpan.TimeSpans});
} catch (e) {}

const oldTrackers = cache.readQuery<Trackers>({query: gqlTimeSpan.Trackers});
if (!data || !data.removeTimeSpan) {
return;
}
const removedId = data.removeTimeSpan.id;
if (oldTrackers) {
cache.writeQuery<Trackers>({
query: gqlTimeSpan.Trackers,
data: {
timers: (oldTrackers.timers || []).filter((tracker) => tracker.id !== removedId),
},
});
}
if (oldData) {
cache.writeQuery<TimeSpans>({
query: gqlTimeSpan.TimeSpans,
data: {
timeSpans: {
__typename: 'PagedTimeSpans',
timeSpans: oldData.timeSpans.timeSpans.filter((ts) => ts.id !== removedId),
cursor: oldData.timeSpans.cursor,
},
},
});
}
},
});
const [removeTimeSpan] = useMutation<RemoveTimeSpan, RemoveTimeSpanVariables>(
gqlTimeSpan.RemoveTimeSpan,
removeTimeSpanOptions
);

const updateNote = (newValue: string) => {
window.clearTimeout(note.current.handle);
Expand Down
80 changes: 77 additions & 3 deletions ui/src/timespan/calendar/CalendarPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {timeRunningCalendar} from '../timeutils';
import {stripTypename} from '../../utils/strip';
import {TimeSpansInRange, TimeSpansInRangeVariables} from '../../gql/__generated__/TimeSpansInRange';
import {ExtendedEventSourceInput} from '@fullcalendar/core/structs/event-source';
import {RemoveTimeSpan, RemoveTimeSpanVariables} from '../../gql/__generated__/RemoveTimeSpan';
import {removeTimeSpanOptions} from '../mutations';

const toMoment = (date: Date): moment.Moment => {
return moment(date).tz('utc');
Expand Down Expand Up @@ -72,6 +74,10 @@ export const CalendarPage: React.FC = () => {
refetchQueries: [{query: gqlTimeSpan.Trackers}],
});
const [updateTimeSpanMutation] = useMutation<UpdateTimeSpan, UpdateTimeSpanVariables>(gqlTimeSpan.UpdateTimeSpan);
const [removeTimeSpan] = useMutation<RemoveTimeSpan, RemoveTimeSpanVariables>(
gqlTimeSpan.RemoveTimeSpan,
removeTimeSpanOptions
);
const [currentDate, setCurrentDate] = React.useState(moment());
const [stopTimer] = useMutation<StopTimer, StopTimerVariables>(gqlTimeSpan.StopTimer, {
update: (cache, {data}) => {
Expand All @@ -93,18 +99,34 @@ export const CalendarPage: React.FC = () => {
window.__TRAGGO_CALENDAR = {};
return () => (window.__TRAGGO_CALENDAR = undefined);
});

const fullCalendarRef = React.useRef<FullCalendar | null>(null);
const unselectCalenderItem = () => {
const instance = fullCalendarRef.current;
if (instance) {
const calendar = instance.getApi();
if (calendar) {
calendar.unselect();
}
}
};

const [ignore, setIgnore] = React.useState<boolean>(false);
const [selected, setSelected] = React.useState<{selected: HTMLElement | null; data: TimeSpans_timeSpans_timeSpans | null}>({
selected: null,
data: null,
});

const [lastCreatedTimeSpanId, setLastCreatedTimeSpanId] = React.useState<number | null>(null);

const [addTimeSpan] = useMutation<AddTimeSpan, AddTimeSpanVariables>(gqlTimeSpan.AddTimeSpan, {
update: (cache, {data}) => {
if (!data || !data.createTimeSpan) {
return;
}
addTimeSpanInRangeToCache(cache, data.createTimeSpan, timeSpansResult.variables);
addTimeSpanToCache(cache, data.createTimeSpan);
unselectCalenderItem();
},
});

Expand Down Expand Up @@ -176,15 +198,19 @@ export const CalendarPage: React.FC = () => {
},
});
};
const onSelect: OptionsInput['select'] = (data) => {
addTimeSpan({
const onSelect: OptionsInput['select'] = async (data) => {
const result = await addTimeSpan({
variables: {
start: moment(data.start).format(),
end: moment(data.end).format(),
tags: [],
note: '',
},
});

if (result.data && result.data.createTimeSpan) {
setLastCreatedTimeSpanId(result.data.createTimeSpan.id);
}
};
const onClick: OptionsInput['eventClick'] = (data) => {
data.jsEvent.preventDefault();
Expand All @@ -197,6 +223,7 @@ export const CalendarPage: React.FC = () => {

// tslint:disable-next-line:no-any
setSelected({data: data.event.extendedProps.ts, selected: data.jsEvent.target as any});
setLastCreatedTimeSpanId(null);
};
if (trackersResult.data && !(trackersResult.data.timers || []).length) {
const startTimerEvent: ExtendedEventSourceInput = {
Expand All @@ -211,10 +238,57 @@ export const CalendarPage: React.FC = () => {
values.push(startTimerEvent);
}

React.useEffect(() => {
let deletingSelected = false;

const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault();
setSelected({selected: null, data: null});
unselectCalenderItem();
}

if (
!deletingSelected &&
lastCreatedTimeSpanId &&
(e.key === 'Delete' || e.key === 'Backspace' || e.key === 'Escape')

This comment has been minimized.

Copy link
@eitch

eitch Mar 8, 2024

I think here the backspace is a bad idea, as this doesn't allow the user to correct typos

) {
e.preventDefault();
removeFromTimeSpanInRangeCache(apollo.cache, lastCreatedTimeSpanId, timeSpansResult.variables);
removeTimeSpan({variables: {id: lastCreatedTimeSpanId}}).finally(() => {
deletingSelected = false;
});
setSelected({selected: null, data: null});
setLastCreatedTimeSpanId(null);
unselectCalenderItem();
return;
}

if (!deletingSelected && selected.data && (e.key === 'Delete' || e.key === 'Backspace')) {
e.preventDefault();
setSelected({selected: null, data: selected.data});
removeFromTimeSpanInRangeCache(apollo.cache, selected.data.id, timeSpansResult.variables);
removeTimeSpan({variables: {id: selected.data.id}}).finally(() => {
deletingSelected = false;
});
setSelected({selected: null, data: null});
unselectCalenderItem();
return;
}
};

document.addEventListener('keydown', onKeyDown);

return () => {
document.removeEventListener('keydown', onKeyDown);
};
}, [selected, lastCreatedTimeSpanId, apollo.cache, removeTimeSpan, timeSpansResult.variables]);

return (
<Paper style={{padding: 10, bottom: 10, top: 80, position: 'absolute'}} color="red">
<FullCalendarStyling>
<FullCalendar
ref={fullCalendarRef}
defaultView="timeGridWeek"
rerenderDelay={30}
datesRender={(x) => {
Expand All @@ -230,7 +304,7 @@ export const CalendarPage: React.FC = () => {
events={values}
allDaySlot={false}
selectable={true}
selectMirror={true}
selectMirror={false}
handleWindowResize={true}
height={'parent'}
selectMinDistance={20}
Expand Down
40 changes: 40 additions & 0 deletions ui/src/timespan/mutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {RemoveTimeSpan, RemoveTimeSpanVariables} from '../gql/__generated__/RemoveTimeSpan';
import * as gqlTimeSpan from '../gql/timeSpan';
import {TimeSpans} from '../gql/__generated__/TimeSpans';
import {Trackers} from '../gql/__generated__/Trackers';
import {MutationHookOptions} from '@apollo/react-hooks/lib/types';

export const removeTimeSpanOptions: MutationHookOptions<RemoveTimeSpan, RemoveTimeSpanVariables> = {
update: (cache, {data}) => {
let oldData: TimeSpans | null = null;
try {
oldData = cache.readQuery<TimeSpans>({query: gqlTimeSpan.TimeSpans});
} catch (e) {}

const oldTrackers = cache.readQuery<Trackers>({query: gqlTimeSpan.Trackers});
if (!data || !data.removeTimeSpan) {
return;
}
const removedId = data.removeTimeSpan.id;
if (oldTrackers) {
cache.writeQuery<Trackers>({
query: gqlTimeSpan.Trackers,
data: {
timers: (oldTrackers.timers || []).filter((tracker) => tracker.id !== removedId),
},
});
}
if (oldData) {
cache.writeQuery<TimeSpans>({
query: gqlTimeSpan.TimeSpans,
data: {
timeSpans: {
__typename: 'PagedTimeSpans',
timeSpans: oldData.timeSpans.timeSpans.filter((ts) => ts.id !== removedId),
cursor: oldData.timeSpans.cursor,
},
},
});
}
},
};

0 comments on commit 6842b5c

Please sign in to comment.