Skip to content

Commit 8043219

Browse files
committed
Support to lock pages when limited time meet (sheepzh#72)
1 parent cab91f5 commit 8043219

File tree

5 files changed

+173
-53
lines changed

5 files changed

+173
-53
lines changed

src/background/timer/save.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,31 @@
55
* https://opensource.org/licenses/MIT
66
*/
77

8+
import TimeLimitItem, { TimeLimitItemLike } from "@entity/dto/time-limit-item"
89
import limitService from "@service/limit-service"
910
import periodService from "@service/period-service"
1011
import timerService from "@service/timer-service"
12+
import { ChromeMessage, ChromeResult } from "@util/message"
1113
import CollectionContext from "./collection-context"
1214

13-
export default function save(collectionContext: CollectionContext) {
15+
function sendLimitedMessage(item: TimeLimitItem[]) {
16+
chrome.tabs.query({ status: "complete" }, tabs => {
17+
tabs.forEach(tab => {
18+
chrome.tabs.sendMessage<ChromeMessage<TimeLimitItemLike[]>, ChromeResult>(tab.id, {
19+
code: "limitTimeMeet",
20+
data: item
21+
}, result => {
22+
if (result?.code === "fail") {
23+
console.error(`Failed to execute limit rule: rule=${JSON.stringify(item)}, msg=${result.msg}`)
24+
} else if (result?.code === "success") {
25+
console.log(`Processed limit rules: rule=${JSON.stringify(item)}`)
26+
}
27+
})
28+
})
29+
})
30+
}
31+
32+
export default async function save(collectionContext: CollectionContext) {
1433
const context = collectionContext.timerContext
1534
if (context.isPaused()) return
1635
timerService.addFocusAndTotal(context.timeMap)
@@ -20,7 +39,9 @@ export default function save(collectionContext: CollectionContext) {
2039
// Add period time
2140
periodService.add(context.lastCollectTime, focusEntry[1].focus)
2241
// Add limit time
23-
limitService.addFocusTime(collectionContext.focusHost, collectionContext.focusUrl, focusEntry[1].focus)
42+
const limitedRules = await limitService.addFocusTime(collectionContext.focusHost, collectionContext.focusUrl, focusEntry[1].focus)
43+
// If time limited after this operation, send messages
44+
limitedRules && limitedRules.length && sendLimitedMessage(limitedRules)
2445
}
2546
context.resetTimeMap()
2647
}

src/content-script/limit.ts

Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,69 @@
55
* https://opensource.org/licenses/MIT
66
*/
77

8-
import TimeLimitItem from "@entity/dto/time-limit-item"
8+
import TimeLimitItem, { TimeLimitItemLike } from "@entity/dto/time-limit-item"
99
import limitService from "@service/limit-service"
1010
import { t2Chrome } from "@util/i18n/chrome/t"
11-
import { ChromeMessage } from "@util/message"
11+
import { ChromeCallback, ChromeMessage, ChromeResult } from "@util/message"
12+
13+
class _Modal {
14+
url: string
15+
mask: HTMLDivElement
16+
delayContainer: HTMLParagraphElement
17+
visible: boolean = false
18+
constructor(url: string) {
19+
this.mask = document.createElement('div')
20+
this.mask.id = "_timer_mask"
21+
this.mask.append(link2Setup(url))
22+
this.url = url
23+
Object.assign(this.mask.style, maskStyle)
24+
}
25+
26+
showModal(showDelay: boolean) {
27+
const _thisUrl = this.url
28+
if (showDelay && this.mask.childElementCount === 1) {
29+
this.delayContainer = document.createElement('p')
30+
this.delayContainer.style.marginTop = '100px'
31+
32+
// Only delay-allowed rules exist, can delay
33+
// @since 0.4.0
34+
const link = document.createElement('a')
35+
Object.assign(link.style, linkStyle)
36+
link.setAttribute('href', 'javascript:void(0)')
37+
const text = t2Chrome(msg => msg.message.more5Minutes)
38+
link.innerText = text
39+
link.onclick = async () => {
40+
await limitService.moreMinutes(_thisUrl)
41+
this.hideModal()
42+
}
43+
this.delayContainer.append(link)
44+
this.mask.append(this.delayContainer)
45+
}
46+
if (this.visible) {
47+
return
48+
}
49+
document.body.append(this.mask)
50+
document.body.style.overflow = 'hidden'
51+
this.visible = true
52+
}
53+
54+
hideModal() {
55+
if (!this.visible) {
56+
return
57+
}
58+
this.mask.remove()
59+
document.body.style.overflow = ''
60+
this.visible = false
61+
}
62+
63+
process(data: TimeLimitItem[]) {
64+
const anyMatch = data.map(item => item.matches(this.url)).reduce((a, b) => a || b)
65+
if (anyMatch) {
66+
const anyDelay = data.map(item => item.matches(this.url) && item.allowDelay).reduce((a, b) => a || b)
67+
this.showModal(anyDelay)
68+
}
69+
}
70+
}
1271

1372
const maskStyle: Partial<CSSStyleDeclaration> = {
1473
width: "100%",
@@ -34,8 +93,6 @@ function messageCode(url: string): ChromeMessage<string> {
3493
return { code: 'openLimitPage', data: encodeURIComponent(url) }
3594
}
3695

37-
let mask: HTMLDivElement
38-
3996
function link2Setup(url: string): HTMLParagraphElement {
4097
const link = document.createElement('a')
4198
Object.assign(link.style, linkStyle)
@@ -49,44 +106,24 @@ function link2Setup(url: string): HTMLParagraphElement {
49106
return p
50107
}
51108

52-
function moreMinutes(url: string, limitedRules: TimeLimitItem[]): HTMLParagraphElement {
53-
const p = document.createElement('p')
54-
p.style.marginTop = '100px'
55-
const canDelayRules = limitedRules.filter(r => r.allowDelay)
56-
57-
if (canDelayRules && canDelayRules.length) {
58-
// Only delay-allowed rules exist, can delay
59-
// @since 0.4.0
60-
const link = document.createElement('a')
61-
Object.assign(link.style, linkStyle)
62-
link.setAttribute('href', 'javascript:void(0)')
63-
const text = t2Chrome(msg => msg.message.more5Minutes)
64-
link.innerText = text
65-
link.onclick = async () => {
66-
await limitService.moreMinutes(url, canDelayRules)
67-
mask.remove()
68-
document.body.style.overflow = ''
69-
}
70-
p.append(link)
71-
}
72-
return p
73-
}
74-
75-
function generateMask(url: string, limitedRules: TimeLimitItem[]): HTMLDivElement {
76-
const modalMask = document.createElement('div')
77-
modalMask.id = "_timer_mask"
78-
modalMask.append(link2Setup(url), moreMinutes(url, limitedRules))
79-
Object.assign(modalMask.style, maskStyle)
80-
return modalMask
81-
}
82-
83109
export default async function processLimit(url: string) {
110+
const modal = new _Modal(url)
84111
const limitedRules: TimeLimitItem[] = await limitService.getLimited(url)
85-
if (!limitedRules.length) return
86-
mask = generateMask(url, limitedRules)
87-
window.onload = () => {
88-
document.body.append(mask)
89-
document.body.style.overflow = 'hidden'
112+
if (limitedRules?.length) {
113+
window.onload = () => modal.showModal(!!limitedRules?.filter?.(item => item.allowDelay).length)
90114
}
115+
chrome.runtime.onMessage.addListener((msg: ChromeMessage<TimeLimitItemLike[]>, _sender, sendResponse: ChromeCallback) => {
116+
if (msg.code !== "limitTimeMeet") {
117+
sendResponse({ code: "ignore" })
118+
return
119+
}
120+
const itemLikes: TimeLimitItemLike[] = msg.data
121+
if (!itemLikes) {
122+
sendResponse({ code: "fail", msg: "Empty time limit item" })
123+
}
124+
const items = itemLikes.map(itemLike => TimeLimitItem.of(itemLike))
125+
modal.process(items)
126+
sendResponse({ code: "success" })
127+
})
91128
}
92129

src/entity/dto/time-limit-item.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
* https://opensource.org/licenses/MIT
66
*/
77

8-
export default class TimeLimitItem {
8+
/**
9+
* @since 0.8.4
10+
*/
11+
export type TimeLimitItemLike = {
912
/**
10-
* Condition, can be regular expression with star signs
11-
*/
13+
* Condition, can be regular expression with star signs
14+
*/
1215
cond: string
1316
regular: RegExp
1417
/**
@@ -21,6 +24,25 @@ export default class TimeLimitItem {
2124
* Waste today, milliseconds
2225
*/
2326
waste?: number
27+
}
28+
29+
export default class TimeLimitItem implements TimeLimitItemLike {
30+
cond: string
31+
regular: RegExp
32+
time: number
33+
enabled: boolean
34+
allowDelay: boolean
35+
waste?: number
36+
37+
static of(like: TimeLimitItemLike) {
38+
return new _Builder()
39+
.cond(like.cond)
40+
.time(like.time)
41+
.enabled(like.enabled)
42+
.allowDelay(like.allowDelay)
43+
.waste(like.waste)
44+
.build()
45+
}
2446

2547
static builder(): _Builder {
2648
return new _Builder

src/service/limit-service.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,29 @@ async function getLimited(url: string): Promise<TimeLimitItem[]> {
6565
* Add time
6666
* @param url url
6767
* @param focusTime time, milliseconds
68+
* @returns the rules is limit cause of this operation
6869
*/
6970
async function addFocusTime(url: string, focusTime: number) {
70-
const allEnabled = await select({ filterDisabled: true, url })
71+
const allEnabled: TimeLimitItem[] = await select({ filterDisabled: true, url })
7172
const toUpdate: { [cond: string]: number } = {}
72-
allEnabled.forEach(item => toUpdate[item.cond] = item.waste += focusTime)
73-
return db.updateWaste(formatTime(new Date, DATE_FORMAT), toUpdate)
73+
const result: TimeLimitItem[] = []
74+
allEnabled.forEach(item => {
75+
const limitBefore = item.hasLimited()
76+
toUpdate[item.cond] = item.waste += focusTime
77+
const limitAfter = item.hasLimited()
78+
if (!limitBefore && limitAfter) {
79+
result.push(item)
80+
}
81+
})
82+
await db.updateWaste(formatTime(new Date, DATE_FORMAT), toUpdate)
83+
return result
7484
}
7585

76-
async function moreMinutes(url: string, rules: TimeLimitItem[]): Promise<void> {
86+
async function moreMinutes(url: string, rules?: TimeLimitItem[]): Promise<void> {
87+
if (rules === undefined || rules === null) {
88+
rules = (await select({ url: url, filterDisabled: true }))
89+
.filter(item => item.hasLimited() && item.allowDelay)
90+
}
7791
const date = formatTime(new Date(), DATE_FORMAT)
7892
const toUpdate: { [cond: string]: number } = {}
7993
rules.forEach(({ cond, waste }) => {
@@ -90,7 +104,16 @@ class LimitService {
90104
updateDelay = updateDelay
91105
select = select
92106
remove = remove
93-
addFocusTime = (host: string, url: string, focusTime: number) => whitelistHolder.notContains(host) && addFocusTime(url, focusTime)
107+
/**
108+
* @returns The rules limited cause of this operation
109+
*/
110+
async addFocusTime(host: string, url: string, focusTime: number): Promise<TimeLimitItem[]> {
111+
if (whitelistHolder.notContains(host)) {
112+
return addFocusTime(url, focusTime)
113+
} else {
114+
return []
115+
}
116+
}
94117
}
95118

96119
export default new LimitService()

src/util/message/index.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,29 @@
55
* https://opensource.org/licenses/MIT
66
*/
77

8-
export type ChromeMessageCode = 'openLimitPage'
8+
export type ChromeMessageCode = 'openLimitPage' | 'limitTimeMeet'
9+
10+
export type ChromeResultCode = "success" | "fail" | "ignore"
11+
912
/**
1013
* Chrome message
1114
* @since 0.2.2
1215
*/
13-
export type ChromeMessage<T> = {
16+
export type ChromeMessage<T = any> = {
1417
code: ChromeMessageCode
1518
data: T
16-
}
19+
}
20+
21+
/**
22+
* @since 0.8.4
23+
*/
24+
export type ChromeResult<T = any> = {
25+
code: ChromeResultCode,
26+
msg?: string
27+
data?: T
28+
}
29+
30+
/**
31+
* @since 0.8.4
32+
*/
33+
export type ChromeCallback<T = any> = (result?: ChromeResult<T>) => void

0 commit comments

Comments
 (0)