scrapbox-pomodoro-2
scrapbox-pomodoro
date-fnsを使ってみた
機能
workとbreakのポモドーロを交互に繰り返す
work, breakが終わったら通知する
最初に許可が必要
Notification.requestPermission()で許可を要求する
workが終わったら今日のページに記録する
2021-03-31 16:31:15
時間を変更できるようにした
s/format/lightFormat
getという手があるのか~
Notification.requestPermission()をconstructorとthis.stopで2回やってる?のはなぜでしょうか?
requestPermission()
は非同期で呼び出しているので、実行完了を待たずに stop()
が呼び出される場合があります awaitすることはできるんだろうか
しなくても実際上は困らないか
workPeriodとかの定数って外に出したほうがいいんですかね?
クラスのメンバ変数にしておいたほうがglobal汚染しなくていいかなと思ったんですが
moduleでカプセル化してあるので、global汚染の心配はありません
export const workPeriod
と指定したとしても、import側で import {workPeriod} from ...
と書いて明示的に呼び出さない限り、import側からアクセスすることは出来ません References
TODO
通知はstopWork, stopBreakそれぞれで出すようにすると実装できそう
記録先のページを開いたときに、そのタブにフォーカスしないようにする?
通知をクリックしたらScrapboxにフォーカスするようにする
同じScrapbox projectを開いているすべてのタブから同じインスタンスを参照する
さすがに無理か?
データだけならlocal storage経由で同期できます
一時停止機能を追加する?
ポモドーロテクニックの型が崩れそう
Eugene Schwartzの33分33秒に対応させたい
(async () => {
const {Pomodoro} = await import('/api/code/programming-notes/scrapbox-pomodoro/script.js');
new Pomodoro({workPeriod: 5 * 1000, breakPeriod: 1 * 1000});
})();
動的importは何がいいんだっけ
/yosider-scriptsは今privateにしてるので他の人では動かないような…
/programming-notesにコピペすればよさそう
import { addMilliseconds, lightFormat } from '../date-fns.min.js/script.js';
const title = 'Pomodoro Timer';
const image = 'https://gyazo.com/978797f03cb0112a0a4cafdf02dcdde8/raw';
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();
JavaScriptのthisの中身は呼ばれ方によって変わる#e0ea7fということなら、
() => this.stopBreak()
の方がわかりやすいと思います 個人差あり
好みの領域かも
-this.start(workPeriod, this.stopWork);
+this.start(workPeriod, () => this.stopWork());
やっていることはscrapbox-pomodoro#361d0bと同じです
constructor()
以外で使っていなさそうだったので でも実際はstopにも2種類あってresetって感じじゃなくなったな
//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/M/d'))}`;
const log = `${lightFormat(this.startTime, 'HH:mm')} -> ${lightFormat(endTime, 'HH:mm')}: [${scrapbox.Page.title}]\n`;
window.open(`${page}?body=${encodeURIComponent(log)}`);
}
stopBreak(msg='') {
this.stop();
this.setItems(this.startWorkButton);
if (msg) new Notification(msg, notificationOptions);
}
setUpdateTimerLoop() {
if (!this.isRunning) return;
残り時間を表示しているitemのタイトルを変更したい
タイトルを関数で生成しているので、itemの位置をtitleから特定できない気がするのですが、なんかいい方法ありますかね?
this.menu.menus.get(title).items[0].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();
}
}