pomodoro-timer-script
code:script.js
//ポロモード.js
(async () => {
const {Pomodoro} = await import('/api/code/yuyasurarin/pomodoro-timer-script/mod.js');
new Pomodoro({workPeriod: 25 * 60 * 1000, breakPeriod: 5 * 60 * 1000});
})();
code:mod.js
import { addMilliseconds, lightFormat } from '/api/code/programming-notes/date-fns.min.js/script.js';
const title = 'Pomodoro Timer';
const notificationOptions = { body: With 🍅 by ${title}, icon: image };
export class Pomodoro {
constructor({workPeriod, breakPeriod} = {}) {
scrapbox.PageMenu.addMenu({title, image, onClick: () => Pomodoro.requestPermission()});
this.menu = scrapbox.PageMenu(title);
//Pomodoro.requestPermission();
//this.stopBreak = this.stopBreak.bind(this);
//this.stopWork = this.stopWork.bind(this);
this.isRunning = false;
this.isWorkPeriod = false;
this.intervalID = undefined;
this.startTime = undefined;
this.endTime = undefined;
this.timeoutID = undefined;
this.workPeriod = workPeriod ?? 25 * 60 * 1000;
this.breakPeriod = breakPeriod ?? 5 * 60 * 1000;
this.setItems(this.startWorkButton);
}
// 定数の設定
get startWorkButton() { return { title: '\uf04b︎ Start Work', onClick: () => this.startWork() };}
get stopWorkButton() { return { title: '\uf04d Stop Work', onClick: () => this.stopWork() };}
get startBreakButton() { return { title: '\uf0f4 Start Break ', onClick: () => this.startBreak() };}
get stopBreakButton() { return { title: '\uf04d Stop Break', onClick: () => this.stopBreak() };}
get timerDisplay() { return { title: '--:--', onClick: () => { } };}
setItems(...items) {
this.menu.removeAllItems();
items.forEach(i => this.menu.addItem(i));
}
start(period, callback) {
this.isRunning = true;
this.startTime = new Date();
this.endTime = addMilliseconds(this.startTime, period);
this.intervalID = setInterval(() => callback(), period);
this.setUpdateTimerLoop();
}
startWork() {
this.start(this.workPeriod, () => this.stopWork('ポモドーロが完了しました。'));
this.setItems(this.timerDisplay, this.stopWorkButton);
}
startBreak() {
this.start(this.breakPeriod, () => this.stopBreak('休憩時間が終了しました。'));
this.setItems(this.timerDisplay, this.stopBreakButton);
}
stop(msg='') {
this.isRunning = false;
clearTimeout(this.timeoutID);
clearInterval(this.intervalID);
}
stopWork(msg='') {
this.stop();
this.setItems(this.startBreakButton);
if (msg) new Notification(msg, notificationOptions);
const endTime = new Date();
const page = /${scrapbox.Project.name}/${encodeURIComponent(lightFormat(endTime, 'yyyy/MM/dd'))};
const log = ${lightFormat(this.startTime, 'HH:mm')} -> ${lightFormat(endTime, 'HH:mm')}: [${scrapbox.Page.title}]\n;
const link = document.createElement("a")
link.href = ${page}?body=${encodeURIComponent(log)} // 必要なら行IDも追記する
document.body.append(link)
link.click()
link.remove()
}
stopBreak(msg='') {
this.stop();
this.setItems(this.startWorkButton);
if (msg) new Notification(msg, notificationOptions);
}
setUpdateTimerLoop() {
if (!this.isRunning) return;
this.menu.menus.get(title).items0.title = lightFormat(this.endTime - new Date(), 'mm:ss'); // itemの0番目に時計があるとする this.menu.emitChange();
this.timeoutID = setTimeout(() => this.setUpdateTimerLoop(), 1000);
}
static requestPermission() {
if (Notification.permission === 'granted') return;
if (Notification.permission === 'denied') throw Error('Permission denied.');
// UI操作を通じて許可申請を出す
const a = document.createElement('a');
a.onclick = async () => {
const state = await Notification.requestPermission();
if (state !== 'granted') throw Error('Permission denied.');
};
document.body.appendChild(a);
a.click();
a.remove();
}
}