lots of stuff

This commit is contained in:
cupcakearmy 2020-09-19 01:16:43 +02:00
parent 36c5ff9a73
commit f469d78ce3
No known key found for this signature in database
GPG Key ID: D28129AE5654D9D9
15 changed files with 351 additions and 1133 deletions

View File

@ -1,10 +1,8 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Borderify", "name": "Ora",
"version": "1.0", "version": "0.1",
"description": "See how much time you spend on each website",
"description": "Adds a red border to all webpages matching mozilla.org.",
"icons": { "icons": {
"512": "icons/timer.png" "512": "icons/timer.png"
}, },
@ -12,7 +10,7 @@
"default_icon": { "default_icon": {
"512": "icons/stopwatch-inv.svg" "512": "icons/stopwatch-inv.svg"
}, },
"default_title": "Ora Dash", "default_title": "Ora Dashboard",
"theme_icons": [ "theme_icons": [
{ {
"light": "./icons/stopwatch-inv.svg", "light": "./icons/stopwatch-inv.svg",

View File

@ -2,7 +2,8 @@
"name": "ora", "name": "ora",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "parcel --no-hmr manifest.json src/dashboard/index.html", "dev": "parcel watch --no-hmr manifest.json src/dashboard/index.html",
"build": "parcel build manifest.json src/dashboard/index.html",
"launch": "web-ext run -s dist" "launch": "web-ext run -s dist"
}, },
"browserslist": [ "browserslist": [
@ -10,17 +11,13 @@
"last 2 firefox versions" "last 2 firefox versions"
], ],
"dependencies": { "dependencies": {
"@amcharts/amcharts4": "^4.10.2",
"chart.js": "^2.9.3",
"dayjs": "^1.8.36", "dayjs": "^1.8.36",
"faker": "^5.1.0", "faker": "^5.1.0",
"google-palette": "^1.1.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"nedb": "^1.8.0", "nedb": "^1.8.0",
"nedb-promises": "^4.0.4", "nedb-promises": "^4.0.4",
"spectre.css": "^0.5.9", "spectre.css": "^0.5.9",
"tailwindcss": "^1.8.10", "tailwindcss": "^1.8.10",
"uhrwerk": "^1.0.2",
"webextension-polyfill": "^0.6.0" "webextension-polyfill": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,13 +1,14 @@
import browser from 'webextension-polyfill' import browser from 'webextension-polyfill'
import { dashboard } from '../shared/utils' import { dashboard } from '../shared/utils'
import { Logs } from '../shared/db' import { insertLog, normalizeTimestamp } from '../shared/db'
browser.browserAction.onClicked.addListener(() => browser.tabs.create({ url: dashboard, active: true })) browser.browserAction.onClicked.addListener(() => browser.tabs.create({ url: dashboard, active: true }))
const frequency = 3000 const frequency = 3000
async function getAllTabs() { async function getAllTabs() {
console.log('Checking...')
const tabs = await browser.tabs.query({}) const tabs = await browser.tabs.query({})
const windows = await browser.windows.getAll() const windows = await browser.windows.getAll()
const active = tabs const active = tabs
@ -23,10 +24,10 @@ async function getAllTabs() {
await Promise.all( await Promise.all(
active.map(({ host }) => { active.map(({ host }) => {
if (host) if (host)
return Logs.insert({ return insertLog({
timestamp: new Date(), timestamp: normalizeTimestamp(new Date()),
host, host,
frequency, seconds: (frequency / 1000) | 0,
}) })
}) })
) )

View File

@ -1,15 +1,17 @@
<script> <script>
import DateInput from './components/Date.svelte' import DateInput from './components/DateInput.svelte'
import Chart from './components/Chart.svelte' // import Chart from './components/Chart.svelte'
import Dev from './components/Dev.svelte' import Dev from './components/Dev.svelte'
import RangeChooser from './components/RangeChooser.svelte'
import { onMount } from 'svelte' import { onMount } from 'svelte'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Duration } from 'uhrwerk'
import { data, countInGroup } from './lib' import { data, countInGroup } from './lib'
import { env } from 'process' import { env } from 'process'
let loading = true
let init = false
let counted let counted
let top = 20 let top = 20
let start = dayjs().subtract(3, 'days').toDate() let start = dayjs().subtract(3, 'days').toDate()
@ -20,12 +22,16 @@
} }
async function calculate() { async function calculate() {
console.log('Calculating') try {
loading = true
const logs = await data({ const logs = await data({
start, start,
end: dayjs(end).endOf('day'), end: dayjs(end).endOf('day'),
}) })
counted = countInGroup(logs) counted = countInGroup(logs)
} finally {
loading = false
}
// const onlyTop = counted.slice(0, top) // const onlyTop = counted.slice(0, top)
// console.log(onlyTop) // console.log(onlyTop)
// topData = { // topData = {
@ -34,13 +40,16 @@
// } // }
} }
$: { $: if (init) {
start, end start, end
calculate() calculate()
} }
onMount(() => { onMount(() => {
calculate() setTimeout(() => {
init = true
// calculate()
}, 250)
}) })
</script> </script>
@ -52,9 +61,9 @@
max-width: 50em; max-width: 50em;
} }
.date > .spacer { /* .date > .spacer {
width: 1em; width: 1em;
} } */
.link { .link {
margin-left: 2em; margin-left: 2em;
@ -70,22 +79,19 @@
<div class="top rounded"> <div class="top rounded">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<h2 class="text-4xl">Top {top}</h2> <h2 class="text-4xl">Top {top}</h2>
<div class="date flex px-1 rounded"> <RangeChooser bind:start bind:end />
<DateInput bind:date={start} />
<div class="spacer" />
<DateInput bind:date={end} />
</div> </div>
</div> {#if loading}
{#if counted} <div class="loading loading-lg" />
<Chart data={counted.slice(0, top)} /> {:else if counted}
<table> <table class="table">
<tr> <tr>
<th>Time Spent</th> <th>Time Spent</th>
<th>Host</th> <th>Host</th>
</tr> </tr>
{#each counted as { host, total, human }} {#each counted as { host, total, human }}
<tr> <tr>
<td><b>{human}</b></td> <td>{human}</td>
<td class="link"><a href={'https://' + host}>{host}</a></td> <td class="link"><a href={'https://' + host}>{host}</a></td>
</tr> </tr>
{/each} {/each}

View File

@ -1,115 +0,0 @@
<script>
import * as am4core from '@amcharts/amcharts4/core'
import * as am4charts from '@amcharts/amcharts4/charts'
import am4themes_frozen from '@amcharts/amcharts4/themes/frozen'
import am4themes_animated from '@amcharts/amcharts4/themes/animated'
import { onMount } from 'svelte'
/* Chart code */
// Themes begin
am4core.useTheme(am4themes_frozen)
am4core.useTheme(am4themes_animated)
let el
let chart
export let data = [
{
network: 'Facebook',
MAU: 2255250000,
},
{
network: 'Google+',
MAU: 430000000,
},
{
network: 'Instagram',
MAU: 1000000000,
},
{
network: 'Pinterest',
MAU: 246500000,
},
{
network: 'Reddit',
MAU: 355000000,
},
{
network: 'TikTok',
MAU: 500000000,
},
{
network: 'Tumblr',
MAU: 624000000,
},
{
network: 'Twitter',
MAU: 329500000,
},
{
network: 'WeChat',
MAU: 1000000000,
},
{
network: 'Weibo',
MAU: 431000000,
},
{
network: 'Whatsapp',
MAU: 1433333333,
},
{
network: 'YouTube',
MAU: 1900000000,
},
]
$: if (chart) {
chart.data = data
}
onMount(() => {
chart = am4core.create(el, am4charts.XYChart)
let categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis())
categoryAxis.renderer.grid.template.location = 0
categoryAxis.dataFields.category = 'host'
categoryAxis.renderer.minGridDistance = 1
categoryAxis.renderer.inversed = true
categoryAxis.renderer.grid.template.disabled = true
let valueAxis = chart.xAxes.push(new am4charts.ValueAxis())
valueAxis.min = 0
let series = chart.series.push(new am4charts.ColumnSeries())
series.dataFields.categoryY = 'host'
series.dataFields.valueX = 'total'
series.columns.template.strokeOpacity = 0
series.columns.template.column.cornerRadiusBottomRight = 5
series.columns.template.column.cornerRadiusTopRight = 5
let labelBullet = series.bullets.push(new am4charts.LabelBullet())
labelBullet.label.horizontalCenter = 'left'
labelBullet.label.dx = 10
labelBullet.label.text = '{values.valueX.workingValue}'
// labelBullet.label.text = "{values.valueX.workingValue.formatNumber('#.0as')}"
labelBullet.locationX = 1
// as by default columns of the same series are of the same color, we add adapter which takes colors from chart.colors color set
series.columns.template.adapter.add('fill', function (fill, target) {
return chart.colors.getIndex(target.dataItem.index)
})
categoryAxis.sortBySeries = series
chart.data = data
})
</script>
<style>
div {
width: 100%;
height: 32em;
}
</style>
<div bind:this={el}>chart</div>

View File

@ -1,68 +1,115 @@
<script> <script>
import * as am4core from '@amcharts/amcharts4/core'
import * as am4charts from '@amcharts/amcharts4/charts'
import am4themes_frozen from '@amcharts/amcharts4/themes/frozen'
import am4themes_animated from '@amcharts/amcharts4/themes/animated'
import { onMount } from 'svelte' import { onMount } from 'svelte'
import Chart from 'chart.js' /* Chart code */
import palette from 'google-palette' // Themes begin
am4core.useTheme(am4themes_frozen)
am4core.useTheme(am4themes_animated)
Chart.defaults.global.legend.display = false let el
let chart
export let type = 'horizontalBar' export let data = [
export let data = {
labels: [],
data: [],
}
export let options = {
scales: {
xAxes: [
{ {
// type: 'logarithmic', network: 'Facebook',
ticks: { MAU: 2255250000,
beginAtZero: true,
}, },
},
],
},
}
let ctx
let mounted
function draw() {
const backgroundColor = palette('rainbow', data.data.length).map((color) => '#' + color + '88')
new Chart(ctx, {
type,
options,
data: {
labels: data.labels,
datasets: [
{ {
data: data.data, network: 'Google+',
// backgroundColor: backgroundColor, MAU: 430000000,
backgroundColor: '#dddddd',
borderColor: '#000000',
borderWidth: 1,
}, },
], {
network: 'Instagram',
MAU: 1000000000,
}, },
}) {
} network: 'Pinterest',
MAU: 246500000,
},
{
network: 'Reddit',
MAU: 355000000,
},
{
network: 'TikTok',
MAU: 500000000,
},
{
network: 'Tumblr',
MAU: 624000000,
},
{
network: 'Twitter',
MAU: 329500000,
},
{
network: 'WeChat',
MAU: 1000000000,
},
{
network: 'Weibo',
MAU: 431000000,
},
{
network: 'Whatsapp',
MAU: 1433333333,
},
{
network: 'YouTube',
MAU: 1900000000,
},
]
$: if (mounted) { $: if (chart) {
data chart.data = data
draw()
} }
onMount(() => { onMount(() => {
mounted = true chart = am4core.create(el, am4charts.XYChart)
let categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis())
categoryAxis.renderer.grid.template.location = 0
categoryAxis.dataFields.category = 'host'
categoryAxis.renderer.minGridDistance = 1
categoryAxis.renderer.inversed = true
categoryAxis.renderer.grid.template.disabled = true
let valueAxis = chart.xAxes.push(new am4charts.ValueAxis())
valueAxis.min = 0
let series = chart.series.push(new am4charts.ColumnSeries())
series.dataFields.categoryY = 'host'
series.dataFields.valueX = 'total'
series.columns.template.strokeOpacity = 0
series.columns.template.column.cornerRadiusBottomRight = 5
series.columns.template.column.cornerRadiusTopRight = 5
let labelBullet = series.bullets.push(new am4charts.LabelBullet())
labelBullet.label.horizontalCenter = 'left'
labelBullet.label.dx = 10
labelBullet.label.text = '{values.valueX.workingValue}'
// labelBullet.label.text = "{values.valueX.workingValue.formatNumber('#.0as')}"
labelBullet.locationX = 1
// as by default columns of the same series are of the same color, we add adapter which takes colors from chart.colors color set
series.columns.template.adapter.add('fill', function (fill, target) {
return chart.colors.getIndex(target.dataItem.index)
})
categoryAxis.sortBySeries = series
chart.data = data
}) })
</script> </script>
<style> <style>
canvas { div {
width: 100%; width: 100%;
height: 20em; height: 32em;
} }
</style> </style>
<canvas bind:this={ctx} /> <div bind:this={el}>chart</div>

View File

@ -0,0 +1,68 @@
<script>
import { onMount } from 'svelte'
import Chart from 'chart.js'
import palette from 'google-palette'
Chart.defaults.global.legend.display = false
export let type = 'horizontalBar'
export let data = {
labels: [],
data: [],
}
export let options = {
scales: {
xAxes: [
{
// type: 'logarithmic',
ticks: {
beginAtZero: true,
},
},
],
},
}
let ctx
let mounted
function draw() {
const backgroundColor = palette('rainbow', data.data.length).map((color) => '#' + color + '88')
new Chart(ctx, {
type,
options,
data: {
labels: data.labels,
datasets: [
{
data: data.data,
// backgroundColor: backgroundColor,
backgroundColor: '#dddddd',
borderColor: '#000000',
borderWidth: 1,
},
],
},
})
}
$: if (mounted) {
data
draw()
}
onMount(() => {
mounted = true
})
</script>
<style>
canvas {
width: 100%;
height: 20em;
}
</style>
<canvas bind:this={ctx} />

View File

@ -1,12 +0,0 @@
<script>
import { onMount } from 'svelte'
import dayjs from 'dayjs'
export let name = ''
export let date = new Date()
let internal = dayjs(date).format('YYYY-MM-DD')
$: date = dayjs(internal, 'YYYY-MM-DD')
</script>
<label class="form-label">{name}<input class="form-input" type="date" bind:value={internal} /> </label>

View File

@ -0,0 +1,19 @@
<script>
import { onMount } from 'svelte'
import dayjs from 'dayjs'
const format = 'YYYY-MM-DD'
export let name = ''
export let date = new Date()
let internal
const input = (x) => (internal = dayjs(x).format(format))
const output = (x) => (date = dayjs(x, format).toDate())
$: input(date)
$: output(internal)
</script>
<input class="form-input input-sm" type="date" bind:value={internal} {name} />

View File

@ -3,29 +3,25 @@
import day from 'dayjs' import day from 'dayjs'
import { range, random } from 'lodash' import { range, random } from 'lodash'
import { Logs } from '../../shared/db' import { insertLog, normalizeTimestamp } from '../../shared/db'
let loading = false let loading = false
async function fill() { async function fill() {
try { try {
loading = true loading = true
const start = day().subtract('7', 'days').valueOf() const start = day().subtract('7', 'days').valueOf()
const end = Date.now() const end = Date.now()
for (const n of range(20)) {
const all = []
for (const n of range(50)) {
const host = faker.internet.domainName() const host = faker.internet.domainName()
for (const m of range(random(500))) { for (const m of range(random(20))) {
const frequency = random(1, 10) * 1000 const date = new Date(random(start, end))
const timestamp = new Date(random(start, end)) const timestamp = normalizeTimestamp(date)
all.push({ host, timestamp, frequency }) const seconds = random(15 * 60)
// console.log(host, date, seconds)
await insertLog({ host, timestamp, seconds })
} }
} }
console.log(`Generated ${all.length} data points`)
console.debug(all)
await Logs.insert(all)
} finally { } finally {
loading = false loading = false
} }

View File

@ -0,0 +1,41 @@
<script>
import dj from 'dayjs'
import DateInput from './DateInput.svelte'
export let start
export let end
function set(interval, amount = 1) {
return () => {
start = dj().subtract(amount, interval).toDate()
end = new Date()
}
}
function all() {
start = new Date(0)
end = new Date()
}
</script>
<style>
.spacer {
width: 0.5em;
}
</style>
<!-- <div class="flex flex-col"> -->
<div class="flex items-center">
<div class="btn-group">
<button class="btn btn-sm" on:click={all}>All</button>
<button class="btn btn-sm" on:click={set('month')}>Month</button>
<button class="btn btn-sm" on:click={set('week')}>Week</button>
<button class="btn btn-sm" on:click={set('day')}>Day</button>
</div>
<div class="spacer" />
<div class="input-group">
<DateInput bind:date={start} />
<DateInput bind:date={end} />
</div>
</div>

View File

@ -1,5 +1,10 @@
import { groupBy, orderBy } from 'lodash' import { each, groupBy, orderBy } from 'lodash'
import { Duration } from 'uhrwerk' import dj from 'dayjs'
import RelativeTime from 'dayjs/plugin/relativeTime'
import Duration from 'dayjs/plugin/duration'
dj.extend(Duration)
dj.extend(RelativeTime)
import { Logs } from '../shared/db' import { Logs } from '../shared/db'
@ -17,9 +22,8 @@ export async function getLogsBetweenDates({ start, end }) {
export function countInGroup(grouped) { export function countInGroup(grouped) {
const counted = Object.entries(grouped).map(([key, data]) => { const counted = Object.entries(grouped).map(([key, data]) => {
const total = data.reduce((acc, cur) => acc + cur.frequency, 0) const total = data.reduce((acc, cur) => acc + cur.seconds, 0)
const human = new Duration(total, 'ms').humanize() const human = dj.duration(total, 'seconds').humanize()
// const human = 'some'
return { return {
host: key, host: key,
total, total,

View File

@ -1,6 +1,3 @@
// import browser from 'webextension-polyfill'
// import '@babel/polyfill'
import App from './App.svelte' import App from './App.svelte'
new App({ target: window.document.getElementById('root') }) new App({ target: window.document.getElementById('root') })

View File

@ -1,6 +1,29 @@
import NeDB from 'nedb-promises' import NeDB from 'nedb-promises'
import day from 'dayjs'
export const Logs = NeDB.create({ export const Logs = NeDB.create({
filename: 'logs.db', filename: 'logs.db',
autoload: true, autoload: true,
}) })
export function normalizeTimestamp(timestamp) {
// Normalize every dato to 15 minutes
const t = day(timestamp)
const min = t.minute()
return t
.millisecond(0)
.second(0)
.minute(min - (min % 15))
.toDate()
}
export async function insertLog({ timestamp, host, seconds }) {
Logs.update(
{
host,
timestamp,
},
{ $inc: { seconds } },
{ upsert: true }
)
}

946
yarn.lock

File diff suppressed because it is too large Load Diff