scrapbox-pomodoro
機能
workとbreakのポモドーロを交互に繰り返す
work, breakが終わったら通知する
最初に許可が必要
2021-03-31 16:31:15
時間を変更できるようにした
s/format/lightFormat
2021-03-14 05:45:50 勝手にいじったtakker.icon
/emoji/thx.iconyosider.icon
getという手があるのか~
constructorで呼び出されたrequestPermission()の実行が完了する前にstop()を呼び出された場合に備えたものですtakker.icon
requestPermission()は非同期で呼び出しているので、実行完了を待たずにstop()が呼び出される場合があります
……ならconstructorで呼び出す必要ないかtakker.icon
stopするときに突然求められるのも変な気がするようなyosider.icon
PageMenuのonClickで許可を求めるようにしてみたyosider.icon
awaitすることはできるんだろうか
しなくても実際上は困らないか
workPeriodとかの定数って外に出したほうがいいんですかね?
クラスのメンバ変数にしておいたほうがglobal汚染しなくていいかなと思ったんですが moduleでカプセル化してあるので、global汚染の心配はありませんtakker.icon export const workPeriodと指定したとしても、import側でimport {workPeriod} from ...と書いて明示的に呼び出さない限り、import側からアクセスすることは出来ません
そうだったのか。。yosider.icon
ESModuleは安全で便利なのですtakker.icon*2
References
TODO
/icons/done.icon自分でボタンを押して止めたときは通知いらない、時間切れのときだけ通知出すようにする
通知はstopWork, stopBreakそれぞれで出すようにすると実装できそう
2021/3/14 21:21 doneyosider.icon
記録先のページを開いたときに、そのタブにフォーカスしないようにする?
通知をクリックしたらScrapboxにフォーカスするようにする
同じScrapbox projectを開いているすべてのタブから同じインスタンスを参照する
さすがに無理か?
なるほど…クリックしたらそれを取ってきて同期するとかかなyosider.icon
一時停止機能を追加する?
code:import.js
(async () => {
const {Pomodoro} = await import('/api/code/programming-notes/scrapbox-pomodoro/script.js');
new Pomodoro({workPeriod: 5 * 1000, breakPeriod: 1 * 1000});
})();
開発コンソールに貼り付けてそのまま実行できるtakker.icon
/icons/なるほど.icon!yosider.icon
もちろん動かないゾtakker.icon
code:script.js
import { addMilliseconds, lightFormat } from '../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();
個人差あり
好みの領域かも
そうしたいんですがどこをどう変えればいいのかわからないですyosider.icon
this.start()を使っているところを変えればいいですtakker.icon
code:diff
-this.start(workPeriod, this.stopWork);
+this.start(workPeriod, () => this.stopWork());
なるほど!yosider.icon
this.reset()をconstructor()に吸収しましたtakker.icon
constructor()以外で使っていなさそうだったので
あ、そういえばstopしたときにresetすればいいと思って分けたのにreset使うの忘れてた…yosider.icon
でも実際はstopにも2種類あってresetって感じじゃなくなったな
code:script.js
//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;
FIXME: itemの0番目に時計があるとするはcommit messageでしょうか?takker.icon
単に後でなんとかしようというメモをしただけですyosider.icon
残り時間を表示しているitemのタイトルを変更したい
タイトルを関数で生成しているので、itemの位置をtitleから特定できない気がするのですが、なんかいい方法ありますかね?
setUpdateTimerLoop()を実行する段階で、item[0]に時計があるのは確定しているので、このままでいいと思いますtakker.icon
addItemの順番が保証されているのか気になったけど、まあされているかyosider.icon
code:script.js
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();
}
}
JavaScript.icon
MDN.icon