Skip to content

Commit 353634b

Browse files
Merge pull request #190 from ActivityWatch/dev/selectable-activity-views
Selectable activity views
2 parents a5a9e70 + bc4e950 commit 353634b

11 files changed

Lines changed: 286 additions & 75 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<template lang="pug">
2+
div
3+
h5 {{ type_title }}
4+
div(v-if="type == 'top_apps'")
5+
aw-summary(:fields="top_apps",
6+
:namefunc="e => e.data.app",
7+
:colorfunc="e => e.data.app",
8+
with_limit)
9+
div(v-if="type == 'top_titles'")
10+
aw-summary(:fields="top_titles",
11+
:namefunc="e => e.data.title",
12+
:colorfunc="e => e.data.app",
13+
with_limit)
14+
div(v-if="type == 'top_domains'")
15+
aw-summary(:fields="top_domains",
16+
:namefunc="e => e.data.$domain",
17+
:colorfunc="e => e.data.$domain",
18+
with_limit)
19+
div(v-if="type == 'top_urls'")
20+
aw-summary(:fields="top_urls",
21+
:namefunc="e => e.data.url",
22+
:colorfunc="e => e.data.$domain",
23+
with_limit)
24+
div(v-if="type == 'top_editor_files'")
25+
aw-summary(:fields="$store.state.activity.editor.top_files",
26+
:namefunc="top_editor_files_namefunc",
27+
:colorfunc="e => e.data.language",
28+
with_limit)
29+
div(v-if="type == 'top_editor_languages'")
30+
aw-summary(:fields="$store.state.activity.editor.top_languages",
31+
:namefunc="e => e.data.language",
32+
:colorfunc="e => e.data.language",
33+
with_limit)
34+
div(v-if="type == 'top_editor_projects'")
35+
aw-summary(:fields="$store.state.activity.editor.top_projects",
36+
:namefunc="top_editor_projects_namefunc",
37+
:colorfunc="e => e.data.language",
38+
with_limit)
39+
div(v-if="type == 'top_categories'")
40+
aw-summary(:fields="top_categories",
41+
:namefunc="e => e.data['$category'].join(' > ')",
42+
:colorfunc="e => e.data['$category'].join(' > ')",
43+
with_limit)
44+
div(v-if="type == 'category_tree'")
45+
aw-categorytree(:events="top_categories")
46+
div(v-if="type == 'category_sunburst'")
47+
aw-sunburst-categories(:data="top_categories_hierarchy", style="height: 20em")
48+
49+
b-dropdown.vis-style-dropdown-btn(size="sm" variant="outline-secondary")
50+
template(v-slot:button-content)
51+
icon(name="cog")
52+
b-dropdown-item(v-for="t in types" :key="t" variant="outline-secondary" @click="$emit('onTypeChange', id, t)" v-bind:disabled="!get_type_available(t)")
53+
| {{ get_type_title(t) }}
54+
</template>
55+
56+
<style scoped lang="scss">
57+
.vis-style-dropdown-btn {
58+
position: absolute;
59+
bottom: 0;
60+
right: 0.5em;
61+
62+
background-color: #fff;
63+
}
64+
</style>
65+
66+
<script>
67+
// TODO: Move this somewhere else
68+
import { build_category_hierarchy } from '~/util/classes';
69+
function pick_subname_as_name(c) {
70+
c.name = c.subname;
71+
c.children = c.children.map(pick_subname_as_name);
72+
return c;
73+
}
74+
75+
export default {
76+
name: 'aw-selectable-vis',
77+
props: {
78+
id: Number,
79+
type: String,
80+
},
81+
data: function() {
82+
return {
83+
types: [
84+
'top_apps',
85+
'top_titles',
86+
'top_domains',
87+
'top_urls',
88+
'top_categories',
89+
'category_tree',
90+
'category_sunburst',
91+
'top_editor_files',
92+
'top_editor_languages',
93+
'top_editor_projects',
94+
],
95+
// TODO: Move this function somewhere else
96+
top_editor_files_namefunc: e => {
97+
let f = e.data.file || '';
98+
f = f.split('/');
99+
f = f[f.length - 1];
100+
return f;
101+
},
102+
// TODO: Move this function somewhere else
103+
top_editor_projects_namefunc: e => {
104+
let f = e.data.project || '';
105+
f = f.split('/');
106+
f = f[f.length - 1];
107+
return f;
108+
},
109+
};
110+
},
111+
computed: {
112+
top_apps: function() {
113+
return this.$store.state.activity.window.top_apps;
114+
},
115+
top_titles: function() {
116+
return this.$store.state.activity.window.top_titles;
117+
},
118+
top_domains: function() {
119+
return this.$store.state.activity.browser.top_domains;
120+
},
121+
top_urls: function() {
122+
return this.$store.state.activity.browser.top_urls;
123+
},
124+
top_categories: function() {
125+
return this.$store.state.activity.category.top;
126+
},
127+
top_categories_hierarchy: function() {
128+
if (this.top_categories) {
129+
const categories = this.top_categories.map(c => {
130+
return { name: c.data.$category, size: c.duration };
131+
});
132+
133+
return {
134+
name: 'All',
135+
children: build_category_hierarchy(categories).map(c => pick_subname_as_name(c)),
136+
};
137+
} else {
138+
return null;
139+
}
140+
},
141+
type_title: function() {
142+
return this.get_type_title(this.type);
143+
},
144+
},
145+
methods: {
146+
get_type_available: function(type) {
147+
if (type === 'top_apps' || type === 'top_titles') {
148+
return this.$store.state.activity.window.available;
149+
} else if (type === 'top_domains' || type === 'top_urls') {
150+
return this.$store.state.activity.browser.available;
151+
} else if (
152+
type === 'top_editor_files' ||
153+
type === 'top_editor_languages' ||
154+
type === 'top_editor_projects'
155+
) {
156+
return this.$store.state.activity.editor.available;
157+
} else if (
158+
type === 'top_categories' ||
159+
type === 'category_tree' ||
160+
type === 'category_sunburst'
161+
) {
162+
return this.$store.state.activity.category.available;
163+
} else {
164+
console.error('Unknown type available: ', type);
165+
return false;
166+
}
167+
},
168+
get_type_title: function(type) {
169+
if (type === 'top_apps') {
170+
return 'Top Applications';
171+
} else if (type === 'top_titles') {
172+
return 'Top Window Titles';
173+
} else if (type === 'top_domains') {
174+
return 'Top Browser Domains';
175+
} else if (type === 'top_urls') {
176+
return 'Top Browser URLs';
177+
} else if (type === 'top_editor_files') {
178+
return 'Top Editor Files';
179+
} else if (type === 'top_editor_languages') {
180+
return 'Top Editor Languages';
181+
} else if (type === 'top_editor_projects') {
182+
return 'Top Editor Projects';
183+
} else if (type === 'top_categories') {
184+
return 'Top Categories';
185+
} else if (type === 'category_tree') {
186+
return 'Category Tree';
187+
} else if (type === 'category_sunburst') {
188+
return 'Category Sunburst';
189+
} else {
190+
console.error('Unknown type: ', type);
191+
return 'Unknown';
192+
}
193+
},
194+
},
195+
};
196+
</script>

src/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Vue.component('error-boundary', () => import('./components/ErrorBoundary.vue'));
3737
Vue.component('input-timeinterval', () => import('./components/InputTimeInterval.vue'));
3838
Vue.component('aw-header', () => import('./components/Header.vue'));
3939
Vue.component('aw-devonly', () => import('./components/DevOnly.vue'));
40+
Vue.component('aw-selectable-vis', () => import('./components/SelectableVisualization.vue'));
4041

4142
// Visualization components
4243
Vue.component('aw-summary', () => import('./visualizations/Summary.vue'));

src/route.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import VueRouter from 'vue-router';
33

44
const Home = () => import('./views/Home.vue');
55

6-
// Daily activity views for desktop
7-
const ActivityDaily = () => import('./views/activity/daily/ActivityDaily.vue');
8-
const ActivityDailySummary = () => import('./views/activity/daily/ActivityDailySummary.vue');
9-
const ActivityDailyWindow = () => import('./views/activity/daily/ActivityDailyWindow.vue');
10-
const ActivityDailyBrowser = () => import('./views/activity/daily/ActivityDailyBrowser.vue');
11-
const ActivityDailyEditor = () => import('./views/activity/daily/ActivityDailyEditor.vue');
12-
6+
// Activity views for desktop
7+
const Activity = () => import('./views/activity/Activity.vue');
8+
const ActivitySummary = () => import('./views/activity/ActivitySummary.vue');
9+
const ActivityWindow = () => import('./views/activity/ActivityWindow.vue');
10+
const ActivityBrowser = () => import('./views/activity/ActivityBrowser.vue');
11+
const ActivityEditor = () => import('./views/activity/ActivityEditor.vue');
1312
const ActivityAndroid = () => import('./views/activity/ActivityAndroid.vue');
13+
1414
const Buckets = () => import('./views/Buckets.vue');
1515
const Bucket = () => import('./views/Bucket.vue');
1616
const QueryExplorer = () => import('./views/QueryExplorer.vue');
@@ -29,34 +29,34 @@ const router = new VueRouter({
2929
{ path: '/activity/android/:host/:date?', component: ActivityAndroid, props: true },
3030
{
3131
path: '/activity/:host/:periodLength?/:date?',
32-
component: ActivityDaily,
32+
component: Activity,
3333
props: true,
3434
children: [
3535
{
3636
path: 'summary',
3737
meta: { subview: 'summary' },
38-
name: 'activity-daily-summary',
39-
component: ActivityDailySummary,
38+
name: 'activity-summary',
39+
component: ActivitySummary,
4040
props: true,
4141
},
4242
{
4343
path: 'window',
4444
meta: { subview: 'window' },
45-
name: 'activity-daily-window',
46-
component: ActivityDailyWindow,
45+
name: 'activity-window',
46+
component: ActivityWindow,
4747
props: true,
4848
},
4949
{
5050
path: 'browser',
5151
meta: { subview: 'browser' },
52-
name: 'activity-daily-browser',
53-
component: ActivityDailyBrowser,
52+
name: 'activity-browser',
53+
component: ActivityBrowser,
5454
},
5555
{
5656
path: 'editor',
5757
meta: { subview: 'editor' },
58-
name: 'activity-daily-editor',
59-
component: ActivityDailyEditor,
58+
name: 'activity-editor',
59+
component: ActivityEditor,
6060
},
6161
// Unspecified should redirect to summary view is the summary view
6262
// (needs to be last since otherwise it'll always match first)

src/store/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Vue from 'vue';
22
import Vuex from 'vuex';
3-
import activity_daily from './modules/activity_daily';
3+
import activity from './modules/activity';
44
import buckets from './modules/buckets';
55
import settings from './modules/settings';
66
//import createLogger from '../../../src/plugins/logger';
@@ -11,7 +11,7 @@ const debug = process.env.NODE_ENV !== 'production';
1111

1212
export default new Vuex.Store({
1313
modules: {
14-
activity_daily,
14+
activity,
1515
buckets,
1616
settings,
1717
},

src/views/Dev.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ export default {
3232
},
3333
methods: {
3434
query_window_timing: async function() {
35-
await this.$store.dispatch('activity_daily/query_window', this.queryOptions);
35+
await this.$store.dispatch('activity/query_window', this.queryOptions);
3636
},
3737
query_browser_timing: async function() {
38-
await this.$store.dispatch('activity_daily/query_browser', this.queryOptions);
38+
await this.$store.dispatch('activity/query_browser', this.queryOptions);
3939
},
4040
},
4141
};

src/views/activity/daily/ActivityDaily.vue renamed to src/views/activity/Activity.vue

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ div
55
p
66
| Host: {{ host }}
77
br
8-
| Active time: {{ $store.state.activity_daily.active.duration | friendlyduration }}
8+
| Active time: {{ $store.state.activity.active.duration | friendlyduration }}
99

1010
div.d-flex
1111
div.p-1
@@ -38,16 +38,16 @@ div
3838

3939
ul.row.nav.nav-tabs.my-3.px-3
4040
li.nav-item
41-
router-link.nav-link(:to="{ name: 'activity-daily-summary', params: $route.params }")
41+
router-link.nav-link(:to="{ name: 'activity-summary', params: $route.params }")
4242
h5 Summary
4343
li.nav-item
44-
router-link.nav-link(:to="{ name: 'activity-daily-window', params: $route.params }")
44+
router-link.nav-link(:to="{ name: 'activity-window', params: $route.params }")
4545
h5 Window
4646
li.nav-item
47-
router-link.nav-link(:to="{ name: 'activity-daily-browser', params: $route.params }")
47+
router-link.nav-link(:to="{ name: 'activity-browser', params: $route.params }")
4848
h5 Browser
4949
li.nav-item
50-
router-link.nav-link(:to="{ name: 'activity-daily-editor', params: $route.params }")
50+
router-link.nav-link(:to="{ name: 'activity-editor', params: $route.params }")
5151
h5 Editor
5252

5353
div
@@ -157,9 +157,7 @@ export default {
157157
return `/activity/${this.host}/${this.periodLength}`;
158158
},
159159
periodusage: function() {
160-
return this.$store.getters['activity_daily/getActiveHistoryAroundTimeperiod'](
161-
this.timeperiod
162-
);
160+
return this.$store.getters['activity/getActiveHistoryAroundTimeperiod'](this.timeperiod);
163161
},
164162
timeperiod: function() {
165163
// TODO: Get start of day/week/month (depending on periodLength) with offset
@@ -230,7 +228,7 @@ export default {
230228
},
231229
232230
refresh: async function(force) {
233-
await this.$store.dispatch('activity_daily/ensure_loaded', {
231+
await this.$store.dispatch('activity/ensure_loaded', {
234232
timeperiod: this.timeperiod,
235233
host: this.host,
236234
force: force,
@@ -240,7 +238,7 @@ export default {
240238
},
241239
242240
load_demo: async function() {
243-
await this.$store.dispatch('activity_daily/load_demo');
241+
await this.$store.dispatch('activity/load_demo');
244242
},
245243
},
246244
};

src/views/activity/daily/ActivityDailyBrowser.vue renamed to src/views/activity/ActivityBrowser.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@ div
66
small Make sure you have a browser watcher installed to use this feature
77
div(v-if="browserBuckets.length > 0")
88

9-
h6 Active browser time: {{ $store.state.activity_daily.browser.duration | friendlyduration }}
9+
h6 Active browser time: {{ $store.state.activity.browser.duration | friendlyduration }}
1010

1111
div.row
1212
div.col-md-6
1313
h5 Top Browser Domains
1414
div(v-if="browserBuckets")
15-
aw-summary(:fields="$store.state.activity_daily.browser.top_domains", :namefunc="e => e.data.$domain", :colorfunc="e => e.data.$domain", with_limit)
15+
aw-summary(:fields="$store.state.activity.browser.top_domains", :namefunc="e => e.data.$domain", :colorfunc="e => e.data.$domain", with_limit)
1616

1717
div.col-md-6
1818
h5 Top Browser URLs
1919
div(v-if="browserBuckets")
20-
aw-summary(:fields="$store.state.activity_daily.browser.top_urls", :namefunc="e => e.data.url", :colorfunc="e => e.data.$domain", with_limit)
20+
aw-summary(:fields="$store.state.activity.browser.top_urls", :namefunc="e => e.data.url", :colorfunc="e => e.data.$domain", with_limit)
2121

2222
//div(v-if="periodLength === 'day'")
2323
br
2424
hr
2525
b-form-checkbox(v-model="timelineShowAFK")
2626
| Show AFK time
27-
aw-timeline-inspect(:chunks="$store.state.activity_daily.web_chunks", :show_afk='timelineShowAFK', :chunkfunc='e => e.data.$domain', :eventfunc='e => e.data.url')
27+
aw-timeline-inspect(:chunks="$store.state.activity.web_chunks", :show_afk='timelineShowAFK', :chunkfunc='e => e.data.$domain', :eventfunc='e => e.data.url')
2828
br
2929
</template>
3030

@@ -49,7 +49,7 @@ export default {
4949
},
5050
computed: {
5151
browserBuckets: function() {
52-
return this.$store.state.activity_daily.buckets.browser_buckets;
52+
return this.$store.state.activity.buckets.browser_buckets;
5353
},
5454
},
5555
};

0 commit comments

Comments
 (0)