mirror of
https://github.com/cupcakearmy/ora.git
synced 2026-04-02 12:05:23 +00:00
progress
This commit is contained in:
14
src/dashboard/components/DurationInput.svelte
Normal file
14
src/dashboard/components/DurationInput.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script>
|
||||
export let value
|
||||
</script>
|
||||
|
||||
{#if Array.isArray(value)}
|
||||
<input type="number" class="form-input" placeholder="1" bind:value={value[0]} />
|
||||
<select class="form-select" bind:value={value[1]}>
|
||||
<option value="m">Minutes</option>
|
||||
<option value="h">Hours</option>
|
||||
<option value="d">Days</option>
|
||||
<option value="w">Weeks</option>
|
||||
<option value="M">Months</option>
|
||||
</select>
|
||||
{/if}
|
||||
@@ -1,9 +1,10 @@
|
||||
<footer>
|
||||
<a href="https://github.com/cupcakearmy/ora" target="_blank" rel="noreferrer">Source Code</a>
|
||||
- v0.8
|
||||
<br />
|
||||
Made with ❤️ by
|
||||
<a href="https://nicco.io" target="_blank" rel="noreferrer">🐘</a>
|
||||
<small>
|
||||
Made with ❤️ by
|
||||
<a href="https://nicco.io" target="_blank" rel="noreferrer">🐘</a>
|
||||
— v0.8 —
|
||||
<a href="https://github.com/cupcakearmy/ora" target="_blank" rel="noreferrer">Source Code</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -19,17 +19,23 @@
|
||||
end = new Date()
|
||||
}
|
||||
|
||||
const intervals = [
|
||||
{ label: 'Year', set: set('year', 1) },
|
||||
{ label: 'Month', set: set('month', 1) },
|
||||
{ label: 'Week', set: set('week', 1) },
|
||||
{ label: '3 Days', set: set('day', 3) },
|
||||
{ label: 'Today', set: set('day', 0) },
|
||||
]
|
||||
|
||||
// Init
|
||||
onMount(() => set('day', 0)())
|
||||
</script>
|
||||
|
||||
<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', 1)}>Month</button>
|
||||
<button class="btn btn-sm" on:click={set('week', 1)}>Week</button>
|
||||
<button class="btn btn-sm" on:click={set('day', 3)}>3 Days</button>
|
||||
<button class="btn btn-sm" on:click={set('day', 0)}>Today</button>
|
||||
{#each intervals as interval (interval.label)}
|
||||
<button class="btn btn-sm" on:click={interval.set}>{interval.label}</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="spacer" />
|
||||
<div class="input-group">
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
import { DB } from '../../shared/db'
|
||||
import { checkForErrors, LimitValidator } from '../../shared/validation'
|
||||
import DurationInput from './DurationInput.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const init = { limit: ['1', 'h'], every: [1, 'd'] }
|
||||
|
||||
export let limit = null
|
||||
export let error = null
|
||||
$: active = limit !== null
|
||||
|
||||
function add() {
|
||||
@@ -23,6 +26,11 @@
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const errors = checkForErrors(LimitValidator, limit)
|
||||
if (errors) {
|
||||
error = errors
|
||||
return
|
||||
}
|
||||
await DB.limits.put(limit)
|
||||
dispatch('update')
|
||||
close()
|
||||
@@ -45,25 +53,11 @@
|
||||
</label>
|
||||
|
||||
<div class="form-label">Rules</div>
|
||||
{#each limit.rules as { limit, every }, i}
|
||||
{#each limit.rules as rule, i}
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-input" placeholder="1" bind:value={limit[0]} />
|
||||
<select class="form-select" bind:value={limit[1]}>
|
||||
<option value="m">Minutes</option>
|
||||
<option value="h">Hours</option>
|
||||
<option value="d">Days</option>
|
||||
<option value="w">Weeks</option>
|
||||
<option value="M">Months</option>
|
||||
</select>
|
||||
<DurationInput bind:value={rule.limit} />
|
||||
<span class="input-group-addon">every</span>
|
||||
<input type="text" class="form-input" bind:value={every[0]} />
|
||||
<select class="form-select" bind:value={every[1]}>
|
||||
<option value="m">Minutes</option>
|
||||
<option value="h">Hours</option>
|
||||
<option value="d">Days</option>
|
||||
<option value="w">Weeks</option>
|
||||
<option value="M">Months</option>
|
||||
</select>
|
||||
<DurationInput bind:value={rule.every} />
|
||||
<button class="btn btn-error input-group-btn" on:click={del(i)}>X</button>
|
||||
</div>
|
||||
{/each}
|
||||
@@ -73,6 +67,9 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div>
|
||||
{#if error}
|
||||
<span class="text-error">{error}</span>
|
||||
{/if}
|
||||
<button on:click={close} class="btn">Cancel</button>
|
||||
<button on:click={save} class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
|
||||
17
src/dashboard/components/StorageQuota.svelte
Normal file
17
src/dashboard/components/StorageQuota.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import pretty from 'pretty-bytes'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
let usage = null
|
||||
|
||||
onMount(async () => {
|
||||
const estimate = await window.navigator.storage.estimate()
|
||||
usage = pretty(estimate.usage)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if usage === null}
|
||||
<span class="loading" />
|
||||
{:else}
|
||||
<span>Storage used: <span class="font-mono">{usage}</span></span>
|
||||
{/if}
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
import RulesEditor from '../components/RulesEditor.svelte'
|
||||
import Rules from '../components/Rules.svelte'
|
||||
@@ -15,7 +16,7 @@
|
||||
}
|
||||
|
||||
function edit(id) {
|
||||
limit = limits.find((limit) => limit.id === id)
|
||||
limit = cloneDeep(limits.find((limit) => limit.id === id))
|
||||
}
|
||||
|
||||
async function load() {
|
||||
|
||||
@@ -1,93 +1,7 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import browser from 'webextension-polyfill'
|
||||
import { saveAs } from 'file-saver'
|
||||
import dj from 'dayjs'
|
||||
|
||||
import FileUpload from '../components/FileUpload.svelte'
|
||||
|
||||
import { dump as dumpDB, load as loadDB, clear as clearDB, validate } from '../../shared/db'
|
||||
import { longPress } from '../../shared/lib'
|
||||
|
||||
const DEFAULT = {
|
||||
retention: 90,
|
||||
}
|
||||
|
||||
let settings = DEFAULT
|
||||
let uploaded
|
||||
let disabled = true
|
||||
|
||||
async function read() {
|
||||
settings = {
|
||||
...DEFAULT,
|
||||
...(await browser.storage.local.get()),
|
||||
}
|
||||
}
|
||||
|
||||
function write() {
|
||||
return browser.storage.local.set(settings)
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await browser.storage.local.clear()
|
||||
await read()
|
||||
}
|
||||
|
||||
async function dump() {
|
||||
const data = await dumpDB()
|
||||
const blob = new Blob([JSON.stringify(data)], { type: 'application/json;charset=utf-8' })
|
||||
const filename = `Ora [${dj().format('YYYY-MM-DD HH-mm-ss')}].json`
|
||||
saveAs(blob, filename)
|
||||
}
|
||||
|
||||
async function clear() {
|
||||
await clearDB()
|
||||
alert('Done')
|
||||
}
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
await loadDB(uploaded)
|
||||
alert('Imported')
|
||||
} catch {
|
||||
alert('Error importing')
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
disabled = !validate(uploaded)
|
||||
}
|
||||
|
||||
onMount(read)
|
||||
import YourData from '../views/YourData.svelte'
|
||||
import Settings from '../views/Settings.svelte'
|
||||
</script>
|
||||
|
||||
<h2 class="mt-8 text-2xl">Settings</h2>
|
||||
<form class="mt-2" on:submit|preventDefault={write}>
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
Retention
|
||||
<small>(Days)</small>
|
||||
<input
|
||||
id="retention"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="3"
|
||||
max="365"
|
||||
step="1"
|
||||
bind:value={settings.retention}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div class="mt-2">
|
||||
<button type="reset" class="btn" on:click={reset}>Reset</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<h2 class="mt-8 text-2xl">Your Data</h2>
|
||||
<div class="mt-2">
|
||||
<FileUpload bind:value={uploaded} />
|
||||
<button class="btn btn-primary" on:click={load} {disabled}>Import</button>
|
||||
<button class="btn btn-primary" on:click={dump}>Export</button>
|
||||
<button class="btn btn-error tooltip" data-tooltip="Hold to delete" use:longPress={clear}>Delete all data</button>
|
||||
</div>
|
||||
<Settings />
|
||||
<YourData />
|
||||
|
||||
47
src/dashboard/views/Settings.svelte
Normal file
47
src/dashboard/views/Settings.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
import { DB } from '../../shared/db'
|
||||
import { SettingsValidator } from '../../shared/validation'
|
||||
|
||||
let settings = null
|
||||
|
||||
async function load() {
|
||||
const values = await DB.settings.toArray()
|
||||
const fromDB = Object.fromEntries(values.map((v) => [v.key, v.value]))
|
||||
settings = SettingsValidator.validate(fromDB).value
|
||||
}
|
||||
|
||||
async function save() {
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
await DB.settings.put({ key, value })
|
||||
}
|
||||
}
|
||||
|
||||
onMount(load)
|
||||
</script>
|
||||
|
||||
<h2 class="mt-8 text-2xl">Settings</h2>
|
||||
{#if settings}
|
||||
<form class="mt-2" on:submit|preventDefault={save}>
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
Retention
|
||||
<small>(Days)</small>
|
||||
<input class="form-input" type="number" min="3" max="365" step="1" bind:value={settings.retention} />
|
||||
</label>
|
||||
<label class="form-label">
|
||||
Idle Timeout
|
||||
<small>(Minutes)</small>
|
||||
<input class="form-input" type="number" min="0" step="1" bind:value={settings.idleTimeout} />
|
||||
<p>Stop tracking after a certain period of idle behavior. <span class="font-mono">0</span> to disable.</p>
|
||||
</label>
|
||||
|
||||
<div class="mt-2">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<div class="loading loading-lg" />
|
||||
{/if}
|
||||
49
src/dashboard/views/YourData.svelte
Normal file
49
src/dashboard/views/YourData.svelte
Normal file
@@ -0,0 +1,49 @@
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import dj from 'dayjs'
|
||||
|
||||
import FileUpload from '../components/FileUpload.svelte'
|
||||
|
||||
import { dump, load, clear } from '../../shared/db'
|
||||
import { checkForErrors, DBValidator } from '../../shared/validation'
|
||||
import { longPress } from '../../shared/lib'
|
||||
|
||||
let uploaded
|
||||
|
||||
async function exportDB() {
|
||||
const data = await dump()
|
||||
const blob = new Blob([JSON.stringify(data)], { type: 'application/json;charset=utf-8' })
|
||||
const filename = `Ora [${dj().format('YYYY-MM-DD HH-mm-ss')}].json`
|
||||
saveAs(blob, filename)
|
||||
}
|
||||
|
||||
async function clearDB() {
|
||||
await clear()
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
async function importDB() {
|
||||
try {
|
||||
await load(uploaded)
|
||||
alert('Imported')
|
||||
} catch {
|
||||
alert('Error importing')
|
||||
}
|
||||
}
|
||||
|
||||
$: disabled = uploaded && !checkForErrors(DBValidator, uploaded)
|
||||
</script>
|
||||
|
||||
<h2 class="mt-8 text-2xl">Your Data</h2>
|
||||
<div class="mt-2">
|
||||
<FileUpload bind:value={uploaded} />
|
||||
<button class="btn btn-primary" on:click={importDB} {disabled}>
|
||||
{#if uploaded && disabled}
|
||||
Invalid data
|
||||
{:else}
|
||||
Import
|
||||
{/if}
|
||||
</button>
|
||||
<button class="btn btn-primary" on:click={exportDB}>Export</button>
|
||||
<button class="btn btn-error tooltip" data-tooltip="Hold to delete" use:longPress={clearDB}>Delete all data</button>
|
||||
</div>
|
||||
Reference in New Issue
Block a user