この記事を作った動機
2025/10/30 から、2025/11/6 に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。
動作の様子
TODOs
2025/11/6 になる間やる項目たち。やりきれなかった分は次に持ち越し。 内部構造を説明する図とかドキュメントを作る OverlayWindow において、リサイズやサイズ制限をオプションで付ける。デフォルトでは無効にして、とりあえず selector においてそれが機能するようにする。 全体的な未保存、保存、バッファーの管理について考える pageType などは現状のままで、増やさず、free と markdown に専念する tasks -> jobs で定期的に実行されるバックエンドの関数群において、pageCleaner.py を実装する。各ノートにある、参照が消され、deleted.json に記録されていて、一定期間たったページを時期が来たら本当にファイルシステム上から削除する実装をする。コード自体は、tasks.py の設定に依存し、現状では10秒ごとに消す時が来ていないかポーリングする簡易的な実装である。 再接続機能が正しく機能しない場合を検証するTODOs
メイン(持ち越し、新規混合)
propertiesとは独立して、commonsの一部として実装する。padStartcommonsが正しく色を更新できないバグを何とかする。
updatePageの割り込みがあった時に、どのページがセーブされたかIDをUUIDをデータとして含めるupdatePageがあったら差分を扱えるように、先にセーブされたデータをバッファーにためるようにして、diffをマージできるようにするupdatePageコマンドのUUID検証ロジックについて正しく機能しているか調べる。
前回からの持ち越し
持ち越し分
except asyncio.CancelledError が mainLoop 内で機能していない可能性について調べる。opening handshake failed
Traceback (most recent call last):
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\http11.py", line 138, in parse
request_line = yield from parse_line(read_line)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\http11.py", line 309, in parse_line
line = yield from read_line(MAX_LINE_LENGTH)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\streams.py", line 46, in read_line
raise EOFError(f"stream ends after {p} bytes, before end of line")
EOFError: stream ends after 0 bytes, before end of line
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\server.py", line 545, in parse
request = yield from Request.parse(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\http11.py", line 140, in parse
raise EOFError("connection closed while reading HTTP request line") from exc
EOFError: connection closed while reading HTTP request line
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\asyncio\server.py", line 356, in conn_handler
await connection.handshake(
File "C:\Users\username\AppData\Roaming\Python\Python312\site-packages\websockets\asyncio\server.py", line 207, in handshake
raise self.protocol.handshake_exc
websockets.exceptions.InvalidMessage: did not receive a valid HTTP request
他の事項
やったことの詳細
Free ページがバグりまくるのを何とかする
別のページを開くと前のページとアイテムが混じったりして地獄のようなことになっていたので修正しようと試みた。それで以下の点を試し、最終的に動いてそうな状況までもっていった。具体的には、Freeページを開いたらuseFreePageItemsStoreにアイテムを追加し、閉じたらuseFreePageItemsStoreからアイテムをなくし、初期化するはずだったが、それがうまくいかないという現象が起こった。別のFreeページに移動しても、うまくuseEffectでは、Freeページを閉じたときの処理を走らせることができず、前のFreeページのアイテムと、新しく開いたFreeページのアイテムが混ざる状態であった。
- Free ページコンポーネント内で、
modifiedという状態変数を保持し、その状態変数がTrueとなった時だけ、実際にデータサーバへセーブするようにリクエストを送る。アイテム追加、削除、やその他もろもろの編集機能は、modified状態変数を変更することで、変更を反映する。 useFreePageItemsStoreにおいて、pageUUID をアイテムに紐づけて管理するようにした。しかし、あまり効果がなくバグを改善するにはこれは至らなかった気がする。またこの修正により、useAppStateのcurrentPageの仕様を変更し、uuid についても管理するようにし、全体の実装もそれに合わせる変更をした。useEffectを使わず、useRefによって初期化を管理するようにした。useEffectを使ったコンポーネント初期化方法は一見するとクリーンアップも同時に実装でき便利なように見えるが、実は意図しないタイミングで発動することが分かり、それがバグを起こし、制御が難しかったことから、Free ページの初期化では使わないことにした。
以前の実装
...
useEffect(() => {
if(currentPage == null){
showMessageBox({
title: "Free Page",
message: "Unable to find the opened page.",
UUID: messageBoxUUID.current,
type: "error"
})
return
}
console.log("Free page init")
for(const item of jsondata.pageData.items){
addItem(item,structuredClone(currentPage.uuid))
}
initComplete.current = true
closed.current = false
return () => {
console.log("Free page end")
initComplete.current = false
closed.current = true
cleanItem()
}
},[])
...
修正した実装
あまり以下の実装におけるuseEffectの使い方はよくないっぽいが、現状だとそのままにしている。
...
useEffect(() => {
// if(currentPage == null){
// showMessageBox({
// title: "Free Page",
// message: "Unable to find the opened page.",
// UUID: messageBoxUUID.current,
// type: "error"
// })
// return
// }
// console.log("Free page init")
// for(const item of jsondata.pageData.items){
// addItem(item,structuredClone(currentPage.uuid))
// }
// initComplete.current = true
// closed.current = false
return () => {
console.log("Free page end")
initComplete.current = false
closed.current = true
cleanItem()
}
},[])
if(!initComplete.current){
cleanItem()
if(currentPage == null){
showMessageBox({
title: "Free Page",
message: "Unable to find the opened page.",
UUID: messageBoxUUID.current,
type: "error"
})
return
}
console.log("Free page init")
for(const item of jsondata.pageData.items){
addItem(item,structuredClone(currentPage.uuid))
}
initComplete.current = true
closed.current = false
}
...
問題の様子と解決した様子
- 問題の様子
- 解決した様子
Commons を Free ページに実装した
Free ページに存在するアイテムが共通でもつプロパティを編集できるUIを作った。まだ変更を正しく反映できないバグや、ActiveItemをUI操作中に維持できていない、色選択の表示がおかしいなどの問題がある。あとRaw DataとZ-indexのボタンに一部未実装部分がある。
tag 機能について進めた
以下のコマンドについて、dataserver側にまだ未実装でファイルだけ作っただけのコマンドと、具体的にどうするかある程度指針として決めるために、ドキュメントを軽く書いた。
- addTag
タグを特定のページに設定する。 - createTag
タグを新規作成する。 - deleteTag
タグを削除する。 - getTagList
既存のタグのリストを表示する。 - queryTag
タグに紐づいたページを検索する。 - removeTag
特定のタグを特定のページから切り離す。
フロントエンドのネットワークログの整理
前の実装だと、いろんなところで websocketのmessageイベントのデータを出力するようになっていて、コンソールが同じような出力で荒れるということがあったので、src\modules\helper\network.tsxに、何を送信して何が返ってきたか、表示するようにまとめてみた。
export function useDatabaseEffects() {
const serverIP = useDatabaseStore((s) => s.serverIP);
const setWebsocket = useDatabaseStore.setState;
const getWebsocket = useDatabaseStore((s) => s.getWebsocket)
// const init = useRef(true)
const showMessage = (event:MessageEvent) => {
console.log(event.data)
try{
console.log(JSON.parse(event.data))
}catch(e) {
console.log("This is not json data.")
}
}
useEffect(() => {
if (!serverIP) return;
const reconnectLoop = () => {
// console.log("reconnect observer")
const websocket = getWebsocket()
if(websocket != null){
// when there is no problem
if(websocket.readyState == WebSocket.OPEN){
// observe the connection
setTimeout(() => {
reconnectLoop()
},interval * 1000)
return
}
}
// when try to reconnect the dataserver
setTimeout(() => {
const websocket = new WebSocket(serverIP);
websocket.addEventListener("message",showMessage)
// websocket.addEventListener("error",reconnectLoop)
// websocket.addEventListener("open",reconnectLoop)
setWebsocket({ websocket: websocket });
},interval / 2 * 1000)
setTimeout(() => {
reconnectLoop()
},interval * 1000)
}
const websocket = new WebSocket(serverIP);
websocket.addEventListener("error",reconnectLoop)
websocket.addEventListener("open",reconnectLoop)
websocket.addEventListener("close",reconnectLoop)
websocket.addEventListener("message",showMessage)
setWebsocket({ websocket: websocket });
return () => {
websocket.close();
};
}, [serverIP, setWebsocket]);
}
ネットワーク接続状態の監視案について思いつく
前フロントエンドのネットワーク再接続周りについて不完全な改善をしたことについては記録したが、その当時はWebsocketのブラウザの実装以外でネットワークの接続状況を確認する方法として、ポーリングし続けるというのがあった。しかし、それだとやはり無駄が多く、より良い正確に効率よく常態判定ができる方法はないか、考えたところ、「コマンドリクエストを送信して、一定時間経過したら、タイムアウト判定する」という方法を思いついた。
初期化時
まず初期化としてデータサーバに接続するとき、一定時間経過後Ping的に一回コマンドを実行し、一定時間内に帰ってくるか見る。
接続時
コマンドをリクエストした際に、同時にタイムアウトの監視を行う。
メリット
- ポーリングしなくて済む
- ブラウザの実装に依存せずに、自由にタイムアウト、接続の可否の判定ができる。
デメリット
- 実装がちょいと煩雑になる
- 接続後はコマンドが実行された後に初めて接続の可否が分かる仕様になっているため、UX的に良くないかもしれない。数十秒から数分単位で粗めにポーリングしつつ、コマンド送信時の応答時間の長さも判定に使うという方針がいいかもしれないとは思った。
Selector の実装をリファクタリングした
Selector の実装が一つのスクリプトに詰まりすぎているのではないかということで、実装をいくつかに分けた。
│ selector.tsx
└─selector
AnEntry.tsx
CreateList.tsx
helper.tsx
init.tsx
selector.tsx
Selector を表示するために、各selectorフォルダ内にあるコンポーネントを結び付け、総合的にUIをまとめて表示する親コンポーネントとしての役割がある。
AnEntry.tsx
各ページや階層構造のエントリに関するUIパーツ一つ相当の実装がある。
CreateList.tsx
各ノートブック内のページや階層構造などをリスト表示する実装がある。
helper.tsx
現状では、updatePageInfoForSelectorという関数があり、notebookの情報を取得するコマンドを実行するためにネットワークrequestを出す実装がある。
init.tsx
コンポーネント生成時に設定したいuseEffectをまとめたもの。
参考にしたサイトとか
- typeof - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof (2025年11月4日) - <input type=“color”> - HTML | MDN
https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/color (2025年11月4日) - HTML input type=“color”
https://www.w3schools.com/tags/att_input_type_color.asp (2025年11月4日) - onmouseout Event
https://www.w3schools.com/jsref/event_onmouseout.asp (2025年11月6日) - formatting - Pad a number with leading zeros in JavaScript - Stack Overflow
https://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript (2025年11月6日)