dismiss & use svelte in client

This commit is contained in:
cupcakearmy 2021-11-23 14:12:38 +01:00
parent a4e0bf1532
commit 4c0a60b14a
No known key found for this signature in database
GPG Key ID: 3235314B4D31232F
11 changed files with 171 additions and 81 deletions

View File

@ -30,8 +30,7 @@
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["./src/client/index.js"],
"css": ["./src/client/index.css"]
"js": ["./src/client/index.js"]
}
],
"web_accessible_resources": ["./icons/watch.png", "./icons/watch-alt.png", "./src/dashboard/index.html"]

View File

@ -4,6 +4,7 @@ import dayjs from 'dayjs'
import { dashboard } from '../shared/utils'
import { insertLog, normalizeTimestamp, DB } from '../shared/db'
import { getSettingsWithDefaults, getUsageForHost, percentagesToBool } from '../shared/lib'
import { DismissValidator, checkForErrors } from '../shared/validation'
browser.browserAction.onClicked.addListener(() => browser.tabs.create({ url: dashboard, active: true }))
@ -65,5 +66,13 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
case 'report':
DB.settings.put({ key: 'lastActivity', value: new Date() })
break
case 'dismiss':
const entry = {
host: message.host,
timestamp: new Date(),
duration: message.duration,
}
if (!checkForErrors(DismissValidator, entry)) DB.dismiss.put(entry)
break
}
})

85
src/client/App.svelte Normal file
View File

@ -0,0 +1,85 @@
<script>
import { blocked } from './blocked'
import { init } from './reporter'
import { buttons, dismiss } from './dismiss'
init()
</script>
<div class="wrapper" class:hidden={!$blocked}>
<div>
<h1>Overtime</h1>
<div>You have no time left on this website.</div>
<hr />
<div class="dismiss">
<div>
<i> dismiss for... </i>
</div>
<div class="links">
{#each buttons as button}
<!-- svelte-ignore a11y-missing-attribute -->
<a on:click={() => dismiss(button.duration)}>{button.label}</a>
{/each}
</div>
</div>
</div>
</div>
<style>
.wrapper {
position: fixed;
color: #111;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #fefefe;
z-index: 999999999;
font-size: 1rem;
padding: 1rem;
user-select: none;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
'Helvetica Neue', sans-serif;
}
.wrapper.hidden {
display: none;
}
@media (prefers-color-scheme: dark) {
.wrapper {
background-color: #111;
color: #eee;
}
}
.wrapper > div {
margin: 3rem auto;
max-width: 25rem;
}
.wrapper h1 {
margin-bottom: 2rem;
}
.dismiss {
display: flex;
flex-direction: row;
}
.dismiss .links {
margin-left: 1rem;
margin-top: -0.1rem;
}
.dismiss .links a {
display: block;
cursor: pointer;
padding: 0.1em;
transition: all 100ms ease-in-out;
}
.dismiss .links a:hover {
transform: translateX(-0.5em);
}
</style>

17
src/client/blocked.js Normal file
View File

@ -0,0 +1,17 @@
import browser from 'webextension-polyfill'
import { readable } from 'svelte/store'
async function check(set) {
if (window.document.hidden) return
const isBlocked = await browser.runtime.sendMessage({
type: 'check',
host: window.location.host,
})
set(isBlocked)
}
export const blocked = new readable(false, (set) => {
check(set)
const interval = setInterval(() => check(set), 1000)
return () => clearInterval(interval)
})

20
src/client/dismiss.js Normal file
View File

@ -0,0 +1,20 @@
import dayjs from 'dayjs'
import * as duration from 'dayjs/plugin/duration'
dayjs.extend(duration)
import browser from 'webextension-polyfill'
export const buttons = [
{ label: '1 minute', duration: dayjs.duration({ minutes: 1 }) },
{ label: '5 minutes', duration: dayjs.duration({ minutes: 5 }) },
{ label: '15 minutes', duration: dayjs.duration({ minutes: 15 }) },
{ label: '1 hours', duration: dayjs.duration({ hours: 1 }) },
]
export function dismiss(duration) {
browser.runtime.sendMessage({
type: 'dismiss',
duration: duration.asMilliseconds(),
host: window.location.host,
})
}

View File

@ -1,33 +0,0 @@
.ora--wrapper {
display: none;
position: fixed;
color: #111;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #eee;
z-index: 999999999;
font-size: 1rem;
padding: 1rem;
text-align: center;
user-select: none;
}
@media (prefers-color-scheme: dark) {
.ora--wrapper {
background-color: #111;
color: #eee;
}
}
.ora--wrapper div {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
'Helvetica Neue', sans-serif;
margin: 3rem auto;
width: 100%;
}
.ora--wrapper div h1 {
margin-bottom: 0.5rem;
}

View File

@ -1,45 +1,5 @@
import browser from 'webextension-polyfill'
import App from './App.svelte'
let wrapper
let lastReported = 0
function init() {
wrapper = window.document.createElement('div')
wrapper.classList.add('ora--wrapper')
wrapper.classList.add('hidden')
const inner = window.document.createElement('div')
inner.innerHTML = `
<h1>Overtime </h1>
<p>You have no time left on this website 🥺</p>
`
wrapper.appendChild(inner)
window.document.body.appendChild(wrapper)
}
async function check() {
if (window.document.hidden) return
const isBlocked = await browser.runtime.sendMessage({
type: 'check',
host: window.location.host,
})
wrapper.style.display = isBlocked ? 'initial' : 'none'
}
init()
check()
setInterval(check, 2000)
function logActivity() {
const now = Date.now()
// Limit reports to once every second
if (now - lastReported < 1000) return
lastReported = now
browser.runtime.sendMessage({
type: 'report',
})
}
window.document.addEventListener('mousemove', logActivity, false)
window.document.addEventListener('keydown', logActivity, false)
window.document.addEventListener('scroll', logActivity, false)
wrapper = window.document.createElement('div')
window.document.body.appendChild(wrapper)
new App({ target: wrapper })

19
src/client/reporter.js Normal file
View File

@ -0,0 +1,19 @@
import browser from 'webextension-polyfill'
let lastReported = 0
function logActivity() {
const now = Date.now()
// Limit reports to once every second
if (now - lastReported < 1000) return
lastReported = now
browser.runtime.sendMessage({
type: 'report',
})
}
export function init() {
window.document.addEventListener('mousemove', logActivity, false)
window.document.addEventListener('keydown', logActivity, false)
window.document.addEventListener('scroll', logActivity, false)
}

View File

@ -18,6 +18,7 @@ DB.version(2).stores({
DB.version(3).stores({
settings: `key, value`,
dismiss: `host, timestamp, duration`,
})
export function normalizeTimestamp(timestamp) {

View File

@ -52,6 +52,12 @@ export function getUsageForRules(host, rules) {
}
export async function getUsageForHost(host) {
const dismiss = await DB.dismiss.where({ host }).first()
if (dismiss) {
const isDismissed = dj().isBefore(dj(dismiss.timestamp).add(dismiss.duration, 'ms'))
if (isDismissed) return []
}
const limit = await DB.limits.where({ host }).first()
if (!limit) return []
return await Promise.all(getUsageForRules(host, limit.rules))

View File

@ -20,6 +20,12 @@ export const LogValidator = Joi.object({
timestamp: Joi.date(),
})
export const DismissValidator = Joi.object({
host: Joi.string(),
timestamp: Joi.date(),
duration: Joi.number(),
})
export const SettingsValidator = Joi.object({
lastActivity: Joi.date()
.default(() => new Date())
@ -29,9 +35,10 @@ export const SettingsValidator = Joi.object({
})
export const DBValidator = Joi.object({
limits: Joi.array().items(LimitValidator),
logs: Joi.array().items(LogValidator),
limits: Joi.array().items(LimitValidator).optional(),
logs: Joi.array().items(LogValidator).optional(),
settings: SettingsValidator.optional(),
dismiss: Joi.array().items(DismissValidator).optional(),
})
export function checkForErrors(validator, data) {