Спільне редагування
Метод getSharedStore() перетворює редактор на вузол сеансу спільного редагування.
Він генерує кожну зміну як потік дій і застосовує дії, які йому надсилають інші редактори, — транспорт між ними (WebSocket, WebRTC чи будь-що інше, здатне передавати JSON) забезпечуєте ви.
Це один із методів ErdEditorElement, і його поєднують із віддаленим зберіганням, коли переслані зміни треба ще й зберігати.
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();
Дія — це простий об’єкт, придатний до серіалізації, який описує одну зміну документа, тож вона без втрат проходить через JSON.stringify дорогою до транспорту.
Дії надходять пакетами: метод subscribe передає вам масив, і цей масив є однією одиницею — пересилайте його цілим і цілим передавайте в dispatch на іншому боці.
Вважайте дію непрозорою й передавайте її без змін, не читаючи й не переписуючи, адже саме службові дані, які вона несе, підтримують синхронізацію редакторів.
Частина того, що робить редактор, залишається в ньому самому.
Виведення таблиці, нотатки чи групи таблиць на передній план, як це робить їх виділення, не надсилається.
Не надсилається й вибір у Statements, Header і Tables на вкладці схеми SQL, який належить вікну, де його зроблено.
Починаючи з версії 4.1.0, оглядач теж залишається у своєму редакторі: не надсилається, чи він відкритий, а також його ширина, згорнуті рядки, рядок, на якому перебуває клавіатурний фокус, положення прокручування дерева, його пошук і параметри.
Не надсилається й підсвічування, яке перехід до зовнішнього ключа вмикає на лінії його зв’язку.
Прокручування, зміна масштабу чи перемикання вкладки надсилаються, але ніколи не зсувають область перегляду іншого користувача й не перемикають його вкладку. Це стосується й прокручування, яке спричиняє перехід з оглядача, і перемикання на вкладку ERD, коли оглядач відкривається з іншої вкладки.
Блокування спільні: останнє блокування чи розблокування налаштування переважає в кожному редакторі, а блокування несе значення редактора, який його ввімкнув, тож кожен редактор і кожна репліка зберігають однакове значення.
Тому блокування Viewport зберігає область перегляду редактора, який його ввімкнув, а блокування Canvas Type — останню вкладку, на якій був цей редактор перед вкладкою налаштувань, де вмикаються блокування.
Вимкнення блокування Language, Table Name Case, Column Name Case чи Bracket Type також задає цьому налаштуванню в кожному редакторі значення, показане в редакторі, який вимкнув блокування, тож усі вони й далі зберігають одне значення. Вимкнення Viewport чи Canvas Type не зсуває нічиєї області перегляду й не перемикає нічиєї вкладки.
Зміна скрипта Before tables чи After tables надсилається повністю, коли його поле втрачає фокус. Два скрипти узгоджуються окремо, тож зміна, яку співавтор вносить в один із них, ніколи не перезаписує інший.
Ніщо, зроблене в режимі Flow на вкладці візуалізації, не є зміною документа, тож редактор у режимі Flow не надсилає іншим жодних змін, а зміни, які надсилають вони, і далі застосовуються, поки він перебуває в цьому режимі.
Єдиний виняток — вихід із режиму Flow на іншу вкладку: це перемикання вкладки надсилається, як і будь-яке інше.
Кнопка зовнішнього посилання на картці, результат швидкого пошуку, вибраний після #, @ чи :, і відкриття оглядача — усе це переводить із режиму Flow на вкладку ERD. Прокручування, яке там спричиняє кнопка на картці чи результат швидкого пошуку, теж надсилається.
Два редактори на одній сторінці
Тут з’єднання 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);
});
Обидва редактори тут починають із того самого нового порожнього документа, тож жодного з них не треба ініціалізувати.
Якщо редакторів три або більше, надсилайте пакети кожного з них усім іншим: редактор ніколи не передає далі того, що отримав.
Ініціалізація співавтора
Спільне сховище пересилає зміни в міру їх внесення й ніколи не передає новому учасникові документ у його поточному стані, тож редакторові, що приєднується до вже запущеного сеансу, спершу треба передати документ.
Починаючи з версії 4.0.0, ініціалізуйте його значенням runtimeValue редактора, який уже є в сеансі, а не значенням value чи збереженим файлом.
editor2.setInitialValue(editor1.runtimeValue);
const sharedStore2 = editor2.getSharedStore();
Значення часу виконання досі містить те, що було видалено після відкриття документа, тож, коли інший редактор згодом відмінить видалення, новий учасник поверне таблицю з її назвою й стовпцями.
value — це форма, яку містить файл, без видалених сутностей, тож новий учасник, ініціалізований із неї чи з файлу, натомість повернув би порожню таблицю без назви.
І надалі зберігайте значення value: значення runtimeValue призначене лише для ініціалізації іншого редактора, другого подання чи replicationStore.
Редактори у VS Code, Obsidian, IDE від JetBrains і вебзастосунку саме так ініціалізують друге подання відкритого документа.
Виклик setInitialValue не пересилається, тож ініціалізація нового учасника нічого не змінює в інших редакторах.
Починаючи з версії 4.0.0, він також відкидає регістри спільного редагування цього редактора, які узгоджують одночасні зміни. Якщо спільне сховище редактора вже мало підписника, сховище знову запитує в інших редакторів їхні регістри, як і під час першого виклику subscribe, тож редактор, повторно завантажений посеред сеансу, узгоджує зміни так само, як інші.
Присвоєння значення value зберігає регістри, але, на відміну від виклику setInitialValue, пересилається: щойно редактор має спільне сховище, кожен редактор у сеансі завантажує присвоєний документ, тож ніколи не ініціалізуйте нового учасника в такий спосіб.
Змішані випуски
Редактор застосовує лише ті типи дій, які знає його власний випуск, тож у сеансі зі змішаними випусками старіші редактори пропускають усе, що передається діями новішого типу.
Починаючи з версії 3.10.0, зміна ON DELETE чи ON UPDATE зв’язку передається як один із двох нових типів дій, relationship.changeOnDelete і relationship.changeOnUpdate, а дія relationship.add може містити обидва поля. Ретранслятору, який передає дії без змін, для них не потрібно нічого нового.
Редактор, випущений до версії 3.10.0, ігнорує ці типи дій і поля: він ніколи не дізнається ON DELETE чи ON UPDATE зв’язку, а документ, збережений у такому редакторі, не містить жодного з них. Зв’язок, який додає старіший редактор, надходить без них, тож і ON DELETE, і ON UPDATE спочатку мають значення Not set.
Усі інші зміни, які надсилає редактор версії 3.10.0, передаються типами дій, які старіші редактори вже знають.
Кнопка Replace All у пошуку й заміні надсилає свої зміни одним пакетом тих самих дій перейменування, коментаря й нотатки, які надсилає зміна, внесена вручну. Параметри відображення Alternate Key і Referential Actions передаються наявною дією settings.changeShow.
Починаючи з версії 4.0.0, такі зміни передаються новими типами дій, і ретранслятору, який передає дії без змін, для них не потрібно нічого нового:
- додавання, переміщення, зміна розміру, перейменування, зміна кольору чи видалення групи таблиць:
tableGroup.add,tableGroup.move,tableGroup.moveTo,tableGroup.resize,tableGroup.changeName,tableGroup.changeColorіtableGroup.remove; приєднання таблиці до групи чи її вихід із групи:table.changeGroup; - натискання
Saveу вікні зіставлення стовпців для наявного зв’язку, яке задає обидва його списки стовпців одночасно:relationship.changeColumns; - увімкнення чи вимкнення блокування:
settings.changeLockSettings; - зміна скрипта
Before tablesчиAfter tablesна вкладці схеми SQL:settings.changeDDLScript.
Параметри відображення Table Groups і Memos передаються наявною дією settings.changeShow.
Дія settings.changeIgnoreSaveSettings, яку надсилали перемикачі Save Scroll Information і Save Zoom Information, доки їх не замінили блокування, більше не надсилається, а дії settings.changeRelationshipOptimization більше немає. Редактор версії 4.0.0, який отримує будь-яку з них, нічого не змінює.
Редактор, випущений до версії 4.0.0, ігнорує нові типи дій: він ніколи не бачить ні груп таблиць, ні того, які таблиці до них входять, хоча перетягування групи все одно переміщує в ньому таблиці групи, бо воно надсилає також їхні дії table.move.
Після натискання Save у вікні зіставлення стовпців він зберігає старі стовпці зв’язку, а коли в ньому видаляють стовпець цього старого зіставлення, зв’язок зникає в усіх редакторах. Блокувань і скриптів у ньому немає.
Документ, збережений у такому редакторі, не містить нічого з цього, а редактор версії 4.0.0 відкриває його з усіма ввімкненими блокуваннями.
Тримайте кожен редактор у сеансі, а також будь-яке сховище replicationStore, що зберігає документ сеансу, на версії 4.0.0 або новішій.
Редактор версії 4.1.0 не надсилає жодного нового типу дій, тож редактори й репліки версій 4.0.0 і 4.1.0 працюють в одному сеансі, нічого не пропускаючи.
Його пошук і заміна охоплюють також назви індексів і назви груп таблиць, а кнопки Replace і Replace All перейменовують їх діями index.changeName і tableGroup.changeName, які редактор версії 4.0.0 уже застосовує.
SharedStoreConfig
getNickname
Задає псевдонім, який показується поруч із курсором миші цього користувача в інших редакторах.
Якщо псевдонім відсутній або порожній, інші користувачі бачать user.
editor.getSharedStore({
getNickname: () => 'nickname...',
});
mouseTracker
Транслює курсор миші цього користувача іншим редакторам. Типове значення — true.
Курсори, надіслані іншими користувачами, завжди показуються на полотні ERD.
editor.getSharedStore({ mouseTracker: false });
focusTracker
Транслює те, з чим працює цей користувач. Типове значення — true.
Таблиця й клітинка, на яких у вас фокус, виділені вами таблиці, нотатки й групи таблиць, а також рамка, яку ви розтягуєте на полотні, надсилаються іншим редакторам і відображаються там.
Кожен користувач отримує власний колір, тож їх можна розрізнити: контур на таблиці у фокусі, підкреслення клітинки у фокусі, обвідка навколо виділених таблиць, нотаток і груп таблиць та штриховий прямокутник для рамки виділення.
Дані присутності, що надходять від інших користувачів, завжди відображаються на вашому полотні незалежно від того, чи ввімкнено цей параметр.
У режимі Flow на вкладці візуалізації фокус і виділення інших користувачів відображаються на картках таблиць, а їхні рамки виділення — ні. Таблиці, які ви там виділяєте, і далі надсилаються; рамка, яку ви там розтягуєте, — ні.
editor.getSharedStore({ focusTracker: false });
Вимкніть обидва трекери, щоб використовувати спільне сховище як звичайний ретранслятор, який не публікує власних даних присутності.
Дані присутності, надіслані іншими редакторами, і далі отримуються й відображаються.
Саме так роблять редактори у VS Code, Obsidian, IDE від JetBrains і Google Drive.
editor.getSharedStore({ mouseTracker: false, focusTracker: false });
Курсор, фокус, виділення й рамка виділення передаються тим самим потоком, що й зміни документа, але вони ефемерні.
Вони ніколи не потрапляють ні в документ, ні в історію змін, ні в editor.value, тож транспорт може спокійно їх відкидати, а replicationStore їх ігнорує.
SharedStore
connection, disconnect
Ці методи задають поточний стан з’єднання.
Поки з’єднання розірвано, зміни зберігаються у внутрішньому буфері.
Буферизовані зміни надсилаються підписникам, щойно передавання знову стає можливим.
Типовий стан — з’єднано (connection).
sharedStore.connection();
sharedStore.disconnect();
dispatch, dispatchSync
Застосовує зміни з інших екземплярів редактора до поточного екземпляра редактора.
sharedStore.dispatch(actions); // async
sharedStore.dispatchSync(actions); // sync
subscribe
Підписується на зміни в редакторі.
Доки немає хоча б одного підписника, ніщо не виходить за межі редактора, а зміни, внесені до того, буферизуються.
Перша підписка також запитує в інших редакторів їхні регістри спільного редагування, які узгоджують одночасні зміни, тож учасник, що приєднався пізніше, узгоджує їх так само.
Документа вона не передає, тож спершу ініціалізуйте учасника, що приєднався пізніше.
Починаючи з версії 4.0.0, сховище запитує їх знову щоразу, коли метод setInitialValue завантажує документ у його редактор після цієї першої підписки.
const unsubscribe = sharedStore.subscribe(actions => {
// send...
});
flushStreamBuffers
Надсилає пакети потоків, які це сховище ще притримує, негайно, а не після їхнього періоду затишшя.
Починаючи з версії 3.9.2, метод працює в сховищі, отриманому з getSharedStore; у версії 3.9.1 він є в типі, але нічого не робить.
Переміщення таблиці, нотатки чи групи таблиць, зміна їхнього кольору, зміна розміру нотатки, а також прокручування чи масштабування області перегляду — кожне з них надходить як потік дрібних змін. Зміна розміру групи таблиць — ні: вона надсилається один раз, коли перетягування завершується.
Сховище збирає кожен потік в один пакет і притримує його, доки в потоці не настане затишшя тривалістю 200 мс. Воно робить це у два етапи, тож пакет надсилається приблизно через 400 мс після останньої зміни.
Виклик завершує це очікування на обох етапах. Поки сховище з’єднане, підписник отримує пакет ще до того, як виклик поверне керування. Якщо нічого не притримано, виклик нічого не надсилає.
Метод destroy, як сховища, так і редактора, відкидає пакет, який ще притримується, тож спершу викличте цей метод, коли відмовляєтеся від сховища, а інші редактори мають зберегти останню зміну, як-от під час закриття вкладки.
sharedStore.flushStreamBuffers();
sharedStore.destroy();
destroy
Повністю знищує екземпляр sharedStore.
Сам редактор лишається недоторканим, тож після виходу із сеансу документ можна й далі редагувати.
Трекери миші й фокусу зупиняються, щойно знищено останній екземпляр sharedStore, виданий редактором.
Пакет потоку, який сховище ще притримує, відкидається разом із ним; щоб надіслати його, спершу викличте flushStreamBuffers.
sharedStore.destroy();