mirror of
https://github.com/cupcakearmy/ora.git
synced 2024-12-21 23:56:31 +00:00
dismiss & use svelte in client
This commit is contained in:
parent
a4e0bf1532
commit
4c0a60b14a
@ -30,8 +30,7 @@
|
|||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["<all_urls>"],
|
"matches": ["<all_urls>"],
|
||||||
"js": ["./src/client/index.js"],
|
"js": ["./src/client/index.js"]
|
||||||
"css": ["./src/client/index.css"]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"web_accessible_resources": ["./icons/watch.png", "./icons/watch-alt.png", "./src/dashboard/index.html"]
|
"web_accessible_resources": ["./icons/watch.png", "./icons/watch-alt.png", "./src/dashboard/index.html"]
|
||||||
|
@ -4,6 +4,7 @@ import dayjs from 'dayjs'
|
|||||||
import { dashboard } from '../shared/utils'
|
import { dashboard } from '../shared/utils'
|
||||||
import { insertLog, normalizeTimestamp, DB } from '../shared/db'
|
import { insertLog, normalizeTimestamp, DB } from '../shared/db'
|
||||||
import { getSettingsWithDefaults, getUsageForHost, percentagesToBool } from '../shared/lib'
|
import { getSettingsWithDefaults, getUsageForHost, percentagesToBool } from '../shared/lib'
|
||||||
|
import { DismissValidator, checkForErrors } from '../shared/validation'
|
||||||
|
|
||||||
browser.browserAction.onClicked.addListener(() => browser.tabs.create({ url: dashboard, active: true }))
|
browser.browserAction.onClicked.addListener(() => browser.tabs.create({ url: dashboard, active: true }))
|
||||||
|
|
||||||
@ -65,5 +66,13 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||||||
case 'report':
|
case 'report':
|
||||||
DB.settings.put({ key: 'lastActivity', value: new Date() })
|
DB.settings.put({ key: 'lastActivity', value: new Date() })
|
||||||
break
|
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
85
src/client/App.svelte
Normal 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
17
src/client/blocked.js
Normal 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
20
src/client/dismiss.js
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
@ -1,45 +1,5 @@
|
|||||||
import browser from 'webextension-polyfill'
|
import App from './App.svelte'
|
||||||
|
|
||||||
let wrapper
|
wrapper = window.document.createElement('div')
|
||||||
let lastReported = 0
|
window.document.body.appendChild(wrapper)
|
||||||
|
new App({ target: wrapper })
|
||||||
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)
|
|
||||||
|
19
src/client/reporter.js
Normal file
19
src/client/reporter.js
Normal 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)
|
||||||
|
}
|
@ -18,6 +18,7 @@ DB.version(2).stores({
|
|||||||
|
|
||||||
DB.version(3).stores({
|
DB.version(3).stores({
|
||||||
settings: `key, value`,
|
settings: `key, value`,
|
||||||
|
dismiss: `host, timestamp, duration`,
|
||||||
})
|
})
|
||||||
|
|
||||||
export function normalizeTimestamp(timestamp) {
|
export function normalizeTimestamp(timestamp) {
|
||||||
|
@ -52,6 +52,12 @@ export function getUsageForRules(host, rules) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsageForHost(host) {
|
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()
|
const limit = await DB.limits.where({ host }).first()
|
||||||
if (!limit) return []
|
if (!limit) return []
|
||||||
return await Promise.all(getUsageForRules(host, limit.rules))
|
return await Promise.all(getUsageForRules(host, limit.rules))
|
||||||
|
@ -20,6 +20,12 @@ export const LogValidator = Joi.object({
|
|||||||
timestamp: Joi.date(),
|
timestamp: Joi.date(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const DismissValidator = Joi.object({
|
||||||
|
host: Joi.string(),
|
||||||
|
timestamp: Joi.date(),
|
||||||
|
duration: Joi.number(),
|
||||||
|
})
|
||||||
|
|
||||||
export const SettingsValidator = Joi.object({
|
export const SettingsValidator = Joi.object({
|
||||||
lastActivity: Joi.date()
|
lastActivity: Joi.date()
|
||||||
.default(() => new Date())
|
.default(() => new Date())
|
||||||
@ -29,9 +35,10 @@ export const SettingsValidator = Joi.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const DBValidator = Joi.object({
|
export const DBValidator = Joi.object({
|
||||||
limits: Joi.array().items(LimitValidator),
|
limits: Joi.array().items(LimitValidator).optional(),
|
||||||
logs: Joi.array().items(LogValidator),
|
logs: Joi.array().items(LogValidator).optional(),
|
||||||
settings: SettingsValidator.optional(),
|
settings: SettingsValidator.optional(),
|
||||||
|
dismiss: Joi.array().items(DismissValidator).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export function checkForErrors(validator, data) {
|
export function checkForErrors(validator, data) {
|
||||||
|
Loading…
Reference in New Issue
Block a user