Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve timeline usability (less clicks to apply time filters) #610

Merged
merged 9 commits into from
Oct 5, 2024
119 changes: 72 additions & 47 deletions src/components/InputTimeInterval.vue
Original file line number Diff line number Diff line change
@@ -1,55 +1,60 @@
<template lang="pug">
div
div
b-alert(v-if="mode == 'range' && invalidDaterange", variant="warning", show)
b-alert(v-if="invalidDaterange", variant="warning", show)
| The selected date range is invalid. The second date must be greater or equal to the first date.
b-alert(v-if="mode == 'range' && daterangeTooLong", variant="warning", show)
b-alert(v-if="daterangeTooLong", variant="warning", show)
| The selected date range is too long. The maximum is {{ maxDuration/(24*60*60) }} days.

div.d-flex.justify-content-between.align-items-end
ErikBjare marked this conversation as resolved.
Show resolved Hide resolved
table
tr
th.pr-2
label(for="mode") Interval mode:
td
select(id="mode", v-model="mode")
option(value='last_duration') Last duration
option(value='range') Date range
tr(v-if="mode == 'last_duration'")
th.pr-2
label(for="duration") Show last:
td
select(id="duration", v-model="duration", @change="valueChanged")
option(:value="15*60") 15min
option(:value="30*60") 30min
option(:value="60*60") 1h
option(:value="2*60*60") 2h
option(:value="4*60*60") 4h
option(:value="6*60*60") 6h
option(:value="12*60*60") 12h
option(:value="24*60*60") 24h
tr(v-if="mode == 'range'")
th.pr-2 Range:
td
input(type="date", v-model="start")
input(type="date", v-model="end")
button(
class="btn btn-outline-dark btn-sm",
type="button",
:disabled="mode == 'range' && (invalidDaterange || emptyDaterange || daterangeTooLong)",
@click="valueChanged"
) Update
table
tr
td.pr-2
label.col-form-label Show last:
td(colspan=2)
.btn-group(role="group")
template(v-for="(dur, idx) in durations")
input(
type="radio"
:id="'dur' + idx"
:value="dur.seconds"
v-model="duration"
@change="applyLastDuration"
).d-none
label(:for="'dur' + idx" v-html="dur.label").btn.btn-light

div(style="text-align:right" v-if="showUpdate && mode=='last_duration'")
tr
td.pr-2
label.col-form-label Show from:
td
input.form-control.d-inline-block.p-1(type="date", v-model="start", style="height: auto; width: auto;")
label.col-form-label.pr-1.pl-2 to:
input.form-control.d-inline.p-1(type="date", v-model="end", style="height: auto; width: auto")
td.text-right
button(
class="btn btn-outline-dark btn-sm",
type="button",
:disabled="invalidDaterange || emptyDaterange || daterangeTooLong",
@click="applyRange"
) Apply

div.mt-1
div.d-inline-block.mr-2(v-if="showUpdate")
b-button.px-2(@click="update()", variant="outline-dark", size="sm")
icon(name="sync")
span.d-none.d-md-inline
| Update
div.mt-1.small.text-muted(v-if="lastUpdate")
| Last update: #[time(:datetime="lastUpdate.format()") {{lastUpdate | friendlytime}}]
| Reload

div.d-inline-block.small.text-muted(v-if="lastUpdate", v-bind:title="lastUpdate")
| Last update: #[time(:datetime="lastUpdate.format()") {{lastUpdate | friendlytime}}]
</template>

<style scoped lang="scss"></style>
<style scoped lang="scss">
.btn-group {
input[type='radio']:checked + label {
background-color: #aaa;
}
}
</style>

<script lang="ts">
import moment from 'moment';
Expand Down Expand Up @@ -77,6 +82,18 @@ export default {
start: null,
end: null,
lastUpdate: null,
durations: [
{ seconds: 0.25 * 60 * 60, label: '&frac14;h' },
{ seconds: 0.5 * 60 * 60, label: '&frac12;h' },
{ seconds: 60 * 60, label: '1h' },
{ seconds: 2 * 60 * 60, label: '2h' },
{ seconds: 3 * 60 * 60, label: '3h' },
{ seconds: 4 * 60 * 60, label: '4h' },
{ seconds: 6 * 60 * 60, label: '6h' },
{ seconds: 12 * 60 * 60, label: '12h' },
{ seconds: 24 * 60 * 60, label: '24h' },
{ seconds: 48 * 60 * 60, label: '48h' },
],
};
},
computed: {
Expand All @@ -103,13 +120,13 @@ export default {
this.duration = this.defaultDuration;
this.valueChanged();

// We want our lastUpdated text to update every ~3s
// We want our lastUpdated text to update every ~500ms
// We can do this by setting it to null and then the previous value.
this.lastUpdateTimer = setInterval(() => {
const _lastUpdate = this.lastUpdate;
this.lastUpdate = null;
this.lastUpdate = _lastUpdate;
}, 1000);
}, 500);
},
beforeDestroy() {
clearInterval(this.lastUpdateTimer);
Expand All @@ -125,11 +142,19 @@ export default {
}
},
update() {
if (this.mode == 'last_duration') {
this.mode = ''; // remove cache on v-model, see explanation: https://github.com/ActivityWatch/aw-webui/pull/344/files#r892982094
this.mode = 'last_duration';
this.valueChanged();
}
const tmpMode = this.mode;
this.mode = '';
this.mode = tmpMode;
this.valueChanged();
},
applyRange() {
this.mode = 'range';
this.duration = 0;
this.valueChanged();
},
applyLastDuration() {
this.mode = 'last_duration';
this.valueChanged();
},
},
};
Expand Down
12 changes: 8 additions & 4 deletions src/views/Timeline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
div
h2 Timeline

input-timeinterval(v-model="daterange", :defaultDuration="timeintervalDefaultDuration", :maxDuration="maxDuration").mb-2
input-timeinterval(v-model="daterange", :defaultDuration="timeintervalDefaultDuration", :maxDuration="maxDuration").mb-3

// blocks
div.d-inline-block.border.rounded.p-2.mr-2
| Events shown: {{ num_events }}
details.d-inline-block.bg-light.small.border.rounded.mr-2.px-2
summary.p-2
b Filters
Expand All @@ -26,7 +24,11 @@ div
select(v-model="filter_client")
option(:value='null') All
option(v-for="client in clients", :value="client") {{ client }}
div(style="float: right; color: #999").d-inline-block.pt-3
div.d-inline-block.border.rounded.p-2.mr-2(v-if="num_events !== 0")
| Events shown: {{ num_events }}
b-alert.d-inline-block.p-2.mb-0.mt-2(v-if="num_events === 0", variant="warning", show)
| No events match selected criteria. Timeline is not updated.
div.float-right.small.text-muted.pt-3
| Drag to pan and scroll to zoom

div(v-if="buckets !== null")
Expand Down Expand Up @@ -63,6 +65,8 @@ export default {
const settingsStore = useSettingsStore();
return Number(settingsStore.durationDefault);
},
// TODO this does not match the actual vis-timeline.chartData which is rendered in the timeline.
// chartData excludes short events.
ErikBjare marked this conversation as resolved.
Show resolved Hide resolved
num_events() {
return _.sumBy(this.buckets, 'events.length');
},
Expand Down
Loading