Skip to content

Commit

Permalink
feat: resumable timers (#42)
Browse files Browse the repository at this point in the history
* feat: resumable timers: resume counting when user taps 'Resume Timer'

Also includes some refactoring/simplification of input-modal

* feat: resume tracking from manually entered time; fix buttons in input modal footer

* fix: only allow manual time input when tracking retrospectively
  • Loading branch information
donalmacanri authored Feb 11, 2023
1 parent 7740ad1 commit 47f26aa
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 93 deletions.
2 changes: 1 addition & 1 deletion src/components/classic-button/classic-button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
</Text>
{#if tracker.started}
<div class="center">
<Counter started={tracker.started} />
<Counter initialDuration={tracker.timeTracked} started={tracker.started} />
</div>
{/if}
</button>
Expand Down
5 changes: 3 additions & 2 deletions src/components/counter/counter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { onMount } from 'svelte'
export let started = undefined
export let initialDuration = 0
export let lg = undefined
export let className = ''
export let color = 'var(--color-red)'
Expand All @@ -19,10 +20,10 @@
const methods = {
init() {
setInterval(() => {
let ms = new Date().getTime() - started
let ms = initialDuration*1000 + new Date().getTime() - started
value = methods.secondsToTime(methods.msToSecond(ms))
}, 1000)
let ms = new Date().getTime() - started
let ms = initialDuration*1000 + new Date().getTime() - started
value = methods.secondsToTime(methods.msToSecond(ms))
},
normalizeTime(time) {
Expand Down
2 changes: 1 addition & 1 deletion src/domains/board/UniboardTrackableGrid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
>
<div slot="subtitle">
{#if trackable.tracker?.started}
<Counter started={trackable.tracker.started} color={trackable.tracker.color} />
<Counter initialDuration={trackable.tracker.timeTracked} started={trackable.tracker.started} color={trackable.tracker.color} />
{/if}
</div>
</ShortcutButton>
Expand Down
4 changes: 2 additions & 2 deletions src/domains/steak/StreakStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
getTrackerInputAsString,
openTrackerInputModal,
getTrackerInputAsString
} from '../tracker/input/TrackerInputStore'

import type { ITrackables } from '../trackable/trackable-utils'
Expand Down Expand Up @@ -35,6 +34,7 @@ export const trackOnThisDay = async (trackable: Trackable, date: Date, known: IT
allowSave: false,
expandNote: true,
nextLabel: `Add to ${dayjs(date).format(dateFormats.tinyDate)} →`,
retrospective: true,
// onClose: closeModal,
})
log.note = response.raw;
Expand Down
38 changes: 32 additions & 6 deletions src/domains/tracker/TrackerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ export const TrackerStore = createKVStore(NPaths.storage.trackers(), {
* @returns Promise<TrackerClass>
*/
const startStopTimer = async (tracker: TrackerClass, ss: 'start' | 'stop'): Promise<TrackerClass> => {
const newTracker = new TrackerClass(tracker)
if (ss == 'stop') {
newTracker.started = undefined
} else {
newTracker.started = new Date().getTime()
}
let newTracker;
await TrackerStore.updateSync((trackerMap) => {
newTracker = new TrackerClass(trackerMap[tracker.tag])
newTracker.timeTracked = trackerMap[tracker.tag].timeTracked || newTracker.default
switch (ss) {
case 'start':
newTracker.started = new Date().getTime()
break
case 'stop':
newTracker.timeTracked += (new Date().getTime() - trackerMap[tracker.tag].started)/1000
newTracker.started = undefined
}
trackerMap[tracker.tag] = newTracker
return trackerMap
})
Expand All @@ -59,6 +64,27 @@ export const stopTimer = async (tracker): Promise<TrackerClass> => {
return await startStopTimer(tracker, 'stop')
}

/**
* Clear a Timer
* @param tracker
* @returns Promise<TrackerClass>
*/
export const resetTimer = async (tracker, timeTracked = 0): Promise<TrackerClass> => {
let newTracker;
await TrackerStore.updateSync((trackerMap) => {
newTracker = new TrackerClass(trackerMap[tracker.tag])

newTracker.timeTracked = timeTracked
newTracker.started = undefined

trackerMap[tracker.tag] = newTracker
return trackerMap
})
InitTrackableStore()

return newTracker
}

/**
* Find Running Timers in a Map
* This is just incase we want to reuse it
Expand Down
2 changes: 2 additions & 0 deletions src/domains/tracker/input/TrackerInputStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type TrackerInputProps = {
raw?: string
suffix?: string
action?: string
retrospective?: boolean
}

const initialState = {
Expand Down Expand Up @@ -259,6 +260,7 @@ export const getTrackerInputAsString = async (props: TrackerInputProps): Promise
allowSave: props.allowSave,
expandNote: props.expandNote,
nextLabel: props.nextLabel,
retrospective: props.retrospective,
onClose: closeModal,
})
}
Expand Down
88 changes: 26 additions & 62 deletions src/domains/tracker/input/input-modal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
import { Lang } from '../../../store/lang'
import Button from '../../../components/button/button.svelte'
import dayjs from 'dayjs'
import ToolbarGrid from '../../../components/toolbar/toolbar-grid.svelte'
import type { TrackerInputProps } from './TrackerInputStore'
import type TrackerClass from '../../../modules/tracker/TrackerClass'
Expand All @@ -41,19 +39,19 @@
import KeyDown from '../../../modules/keyDown/keyDown.svelte'
import { openTrackableEditor } from '../../trackable/trackable-editor/TrackableEditorStore'
import { startTimer, stopTimer } from '../TrackerStore'
import { startTimer, stopTimer, resetTimer } from '../TrackerStore'
import BackdropModal from '../../../components/backdrop/backdrop-modal.svelte'
import { closeModal } from '../../../components/backdrop/BackdropStore2'
import type { TrackerInputResponseType } from './tracker-input-utils'
// Props
// export let tracker = ; // You can provide a tracker
export let value = undefined // If a valid is provided
export let saveLabel = Lang.t('general.save', 'Save') // The label of the save Button
export let nextLabel = Lang.t('general.next', 'Next') // The label of the save Button
let tracker: TrackerClass | undefined = undefined
let manual: boolean = false
export let id: string
export let payload: TrackerInputProps
Expand All @@ -64,7 +62,6 @@
let data = {
value: null, // holds current value
tracker: null, // holds current tracker
ready: false,
suffix: '',
calcUsed: false, // when it's ready
Expand All @@ -73,7 +70,7 @@
note: undefined as undefined | string,
}
// Set up the Methodsx
// Set up the Methods
const methods = {
// When the Save is hit
onSave() {
Expand All @@ -88,6 +85,7 @@
note: `#${tracker.tag}${data.value ? `(${data.value})` : ``} ${tracker.getIncluded(data.value)}`.trim(),
}
// dispatch('save', results)
resetTimer(tracker)
payload.onComplete(results)
closeModal(id)
data.saving = false
Expand All @@ -96,6 +94,7 @@
// When Add is hit
onAdd() {
// Dispatch add
resetTimer(tracker)
payload.onComplete({
value: data.value,
action: 'add',
Expand All @@ -106,58 +105,27 @@
closeModal(id)
},
async onCancel() {
if (tracker.type === 'timer' && !tracker.started) {
await resetTimer(tracker)
}
closeModal(id)
// $Interact.trackerInput.show = false;
// if (!$Interact.trackerInput.allowSave) {
// dispatch("cancelAll");
// } else {
// dispatch("cancel");
// }
},
// When the user starts the time
startTimer() {
// Set the date to epoch time (best to avoid timezones);
let startingDate = dayjs().subtract(data.value, 'second')
data.tracker.started = startingDate.toDate().getTime()
async startTimer() {
// Start the Timer for this tracker
startTimer(data.tracker)
tracker = await startTimer(tracker)
methods.onCancel()
},
// Stop the Timer
stopTimer() {
async stopTimer() {
// Get the Seconds between now and when the tracker started
if (data.tracker.started) {
data.value = (new Date().getTime() - tracker.started) / 1000
// Clear local
data.tracker.started = undefined
// tell store to stop timer
stopTimer(data.tracker)
tracker.started = undefined
if (tracker.started) {
tracker = await stopTimer(tracker)
data.value = tracker.timeTracked
}
},
}
// If Tracker Changes
// FIres each time something happens to this object
$: if (tracker && data.tracker && data.tracker !== tracker) {
// Set to local variable
setTimeout(() => {
// Set to not ready and the new tracker
data.ready = true // TODO: make this lack janky
data.tracker = tracker
data.value = tracker.default || 0
data.ready = true
data.suffix = ''
}, 1)
}
$: if (!payload.tracker) {
data.ready = true // TODO: make this lack janky
data.tracker = tracker
data.value = 0
data.suffix = ''
}
function editTracker() {
openTrackableEditor(tracker.toTrackable())
// TrackerStore.trackerOptions(tracker, {
Expand All @@ -176,20 +144,18 @@
data.value = value
} else {
data.value = tracker.default || 0
value = data.value
}
data.tracker = tracker
if (payload.retrospective) {
manual = true
}
setTimeout(() => {
data.ready = true
}, 12)
}
let lastTracker: TrackerClass | undefined = undefined
$: if (payload.tracker && lastTracker !== payload.tracker) {
lastTracker = payload.tracker
data.suffix = ''
initialize()
}
initialize();
</script>

<BackdropModal className="tracker-input-modal">
Expand Down Expand Up @@ -257,14 +223,12 @@
/>
{:else if tracker.type === 'value' || tracker.type === 'tick'}
<NCalculator
{value}
value={data.value}
displayFormat={(input) => {
return tracker.displayValue(input || '')
}}
on:change={(changedValue) => {
data.value = changedValue.detail
value = changedValue.detail
data = data
}}
/>
{:else if tracker.type === 'timer'}
Expand All @@ -273,12 +237,13 @@
class="filler flex items-center justify-center"
>
<NTimer
tracker={data.tracker}
bind:value={data.value}
{tracker}
{manual}
value={data.value}
on:forceStart={methods.startTimer}
on:change={(event) => {
on:change={async (event) => {
data.value = event.detail
value = event.detail
tracker = await resetTimer(tracker, event.detail)
}}
/>
</div>
Expand All @@ -291,7 +256,6 @@
}}
on:change={(value) => {
data.value = value.detail
value = value.detail
}}
/>
</div>
Expand Down
26 changes: 8 additions & 18 deletions src/domains/tracker/input/timer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
*/
// svelte
import { onMount } from 'svelte'
import { createEventDispatcher } from 'svelte'
// Components
import Counter from '../../../components/counter/counter.svelte'
import ManualTime from '../../../components/counter/manual-time.svelte'
import type TrackerClass from '../../../modules/tracker/TrackerClass'
// Stores
Expand All @@ -22,26 +22,16 @@
const dispatch = createEventDispatcher()
// Props
export let value
export let tracker: any
// Data
let data = {
tempValue: (value || '') + '' || '',
changed: false,
started: tracker.started,
}
onMount(() => {
data.tempValue = value
})
export let value: number
export let tracker: TrackerClass
export let manual: boolean = false
</script>

<div class="n-timer-input w-full">
{#if tracker.started}
{#if !manual && tracker.started}
<div class="flex flex-col items-center justify-center">
<div class="filler" />
<Counter started={tracker.started} lg className="py-5 bg-light" on:change={(event) => {}} />
<Counter initialDuration={tracker.timeTracked} started={tracker.started} lg className="py-5 bg-light" />
<div class="filler" />
</div>
{:else}
Expand All @@ -54,9 +44,9 @@
dispatch('change', event.detail)
}}
/>
{#if !tracker.started && value}
{#if !manual && value}
<button
aria-label="Start Timer"
aria-label="Resume Timer"
on:click={() => {
dispatch('forceStart')
}}
Expand Down
2 changes: 1 addition & 1 deletion src/domains/tracker/timers/timers-modal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
>
<TrackableAvatar {trackable} slot="left" />
<h1 class="ntitle py-3">{trackable.label}</h1>
<Counter slot="right" started={trackable.tracker.started} color={trackable.tracker.color} />
<Counter slot="right" initialDuration={trackable.tracker.timeTracked} started={trackable.tracker.started} color={trackable.tracker.color} />
</ListItem>
{#if index < trackables.length - 1}
<Divider center />
Expand Down
Loading

0 comments on commit 47f26aa

Please sign in to comment.