共同編集
getSharedStore() は、エディタを共同編集セッションのノードにします。
すべての変更をアクションのストリームとして発行し、他のエディタから送られてきたアクションを適用します。両者をつなぐトランスポート(WebSocket、WebRTC、その他 JSON を運べるもの)は利用者側で用意します。
ErdEditorElement のメソッドの 1 つで、中継した変更を保存する必要がある場合は リモート保存 と組み合わせて使います。
interface ErdEditorElement extends HTMLElement {
// ...
getSharedStore: (
config?: SharedStoreConfig & {
mouseTracker?: boolean;
focusTracker?: boolean;
}
) => SharedStore;
}
type SharedStoreConfig = {
getNickname?: () => string;
};
type SharedStore = {
connection: () => void;
disconnect: () => void;
dispatch: (actions: Array<AnyAction> | AnyAction) => void;
dispatchSync: (actions: Array<AnyAction> | AnyAction) => void;
subscribe: (fn: (value: AnyAction[]) => void) => Unsubscribe;
flushStreamBuffers: () => void;
destroy: () => void;
};
// example
const sharedStore = editor.getSharedStore();
アクションはドキュメントへの 1 つの変更を表す単純なシリアライズ可能なオブジェクトであり、トランスポートへ渡す途中で JSON.stringify を通しても壊れません。
アクションはバッチで届き、subscribe が渡す配列が 1 つの単位となるため、配列全体をそのまま中継し、受け取り側でも配列全体をそのまま dispatch します。
アクションが持つ管理情報がエディタ間の同期を支えているため、内容を読んだり書き換えたりせず、不透明な値としてそのまま受け渡します。
3.10.0 からは、リレーションシップの ON DELETE または ON UPDATE の変更は、2 つの新しいアクションの種類 relationship.changeOnDelete と relationship.changeOnUpdate のいずれかとして送られ、relationship.add は両方を持つことができます。アクションをそのまま受け渡す中継であれば、これらのために新たに対応する必要はありません。
3.10.0 より前のエディタは、これらのアクションの種類とフィールドを無視します。リリースが混在するセッションでは、古いエディタにはリレーションシップの ON DELETE も ON UPDATE も伝わらず、古いエディタで保存したドキュメントにはどちらも含まれません。古いエディタが追加したリレーションシップはこれらを持たずに届くため、どちらも Not set で始まります。
セッション内のすべてのエディタと、そのドキュメントを保存する replicationStore には、3.10.0 以降を使います。
3.10.0 のエディタが送るそれ以外の変更は、どれも古いエディタがすでに知っているアクションの種類で送られます。
検索と置換の Replace All は、手で編集したときと同じ名前の変更、コメント、メモのアクションを 1 つのバッチにまとめて送ります。Alternate Key と Referential Actions の表示オプションは、既存の settings.changeShow アクションで送られます。
Visualization タブの Flow モードで行う操作はどれもドキュメントの変更ではないため、Flow を表示しているエディタは他のエディタに変更を送らず、その間も他のエディタから送られてくる変更は引き続き適用されます。
唯一の例外は Flow から別のタブへ移ることで、そのタブの切り替えは他の変更と同じように送信されます。
カードの外部リンクボタン、#、@、: の後に選んだクイック検索の結果、検索と置換を開く操作は、どれも Flow から ERD タブへ移ります。カードのボタンやクイック検索の結果がそこで行うスクロールも送信されます。
1 つのページに 2 つのエディタ
ここでの subscribe から dispatch への接続はネットワークの代わりであり、トランスポートを挟まずにセッションの形を確認できます。
const editor1 = document.createElement('erd-editor');
const editor2 = document.createElement('erd-editor');
const sharedStore1 = editor1.getSharedStore({
getNickname: () => 'editor1',
});
const sharedStore2 = editor2.getSharedStore({
getNickname: () => 'editor2',
});
sharedStore1.subscribe(actions => {
sharedStore2.dispatch(actions);
});
sharedStore2.subscribe(actions => {
sharedStore1.dispatch(actions);
});
SharedStoreConfig
getNickname
他のエディタで、このユーザーのマウスカーソルの横に表示する nickname を設定します。
nickname が未設定または空の場合、他のユーザーには user と表示されます。
editor.getSharedStore({
getNickname: () => 'nickname...',
});
mouseTracker
このユーザーのマウスカーソルを他のエディタへ送信します。既定値は true です。
他のユーザーから送られてきたカーソルは、ERD キャンバスに常に表示されます。
editor.getSharedStore({ mouseTracker: false });
focusTracker
このユーザーが作業している対象を他のエディタへ送信します。既定値は true です。
フォーカスしているテーブルとセル、選択しているテーブルとメモ、キャンバス上でドラッグしているボックスが他のエディタへ送られ、そこに描画されます。
ユーザーごとに固有の色が割り当てられるため、フォーカス中のテーブルの輪郭線、フォーカス中のセルの下線、選択中のテーブルとメモを囲む枠線、ドラッグボックスの破線の矩形で、それぞれを見分けられます。
他のユーザーから届いた操作状況は、このオプションの設定にかかわらず常に自分のキャンバスに描画されます。
Visualization タブの Flow モードでは、他のユーザーのフォーカスと選択はテーブルのカードに描画されますが、ドラッグボックスは描画されません。そこで選択したテーブルは引き続き送信されますが、そこでドラッグしたボックスは送信されません。
editor.getSharedStore({ focusTracker: false });
両方のトラッカーを無効にすると、自身の操作状況を一切送信しない単純な中継として shared store を使えます。
他のエディタから送られてくる操作状況は、引き続き受信して描画します。
VS Code、Obsidian、JetBrains IDE、Google Drive のエディタはこの方式で動作しています。
editor.getSharedStore({ mouseTracker: false, focusTracker: false });
マウスカーソル、フォーカス、選択、ドラッグボックスは、ドキュメントの変更と同じストリームを流れますが、一時的なものです。
これらはドキュメント、Undo の履歴、editor.value のいずれにも入らないため、トランスポートが破棄しても構わず、replicationStore も無視します。
SharedStore
connection, disconnect
現在の接続状態を設定するメソッドです。
disconnect の状態では、内部で発生した変更をバッファに保存します。
送信可能な状態になると、バッファに溜まった変更を購読者へ発行します。
既定の状態は接続済み(connection)です。
sharedStore.connection();
sharedStore.disconnect();
dispatch, dispatchSync
他のエディタインスタンスの変更を、現在のエディタインスタンスに反映します。
sharedStore.dispatch(actions); // async
sharedStore.dispatchSync(actions); // sync
subscribe
エディタの変更を購読します。
購読者が 1 つも存在しない間はエディタから何も送信されず、それまでの変更はバッファに保存されます。
最初の subscribe では他のエディタに現在の状態を要求するため、後から参加した場合でも追いつけます。
const unsubscribe = sharedStore.subscribe(actions => {
// send...
});
flushStreamBuffers
この store がまだ送信せずに保持しているストリームのバッチを、変更が途切れるのを待たずに今すぐ送信します。
3.9.2 からは getSharedStore が返す store で動作します。3.9.1 では型にメソッドはありますが、何もしません。
テーブルやメモの移動、色の変更、メモのサイズ変更、ビューのスクロールや拡大・縮小は、それぞれ小さな変更のストリームとして届きます。
store は各ストリームを 1 つのバッチにまとめ、ストリームの変更が 200ms 途切れるまで保持します。これを 2 段階で行うため、バッチが送信されるのは最後の変更から約 400ms 後です。
呼び出すと、両方の段階でその待機が終わります。store が接続済みの状態であれば、購読者は呼び出しが戻る前にバッチを受け取ります。保持しているものがない場合は、呼び出しても何も送信されません。
store 自身の destroy もエディタの destroy も、まだ保持しているバッチを破棄します。そのため、タブを閉じるときのように、store を手放しても他のエディタに最後の変更を残したい場合は、先にこれを呼び出します。
sharedStore.flushStreamBuffers();
sharedStore.destroy();
destroy
sharedStore のインスタンスを完全に破棄します。
エディタ自体はそのまま残るため、セッションから抜けてもドキュメントの編集を続けられます。
エディタが返した最後の sharedStore が破棄されると、マウスとフォーカスのトラッカーは停止します。
store がまだ保持しているストリームのバッチも一緒に破棄されます。送信するには、先に flushStreamBuffers を呼び出します。
sharedStore.destroy();