עריכה שיתופית
המתודה 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 נשלחת בשלמותה כשהשדה שלו מאבד את הפוקוס. כל אחד משני הסקריפטים מוכרע בנפרד, כך שעריכה של שותף לעריכה באחד מהם לעולם אינה דורסת את השני.
שום דבר שנעשה במצב זרימה של כרטיסיית ויזואליזציה אינו שינוי במסמך, ולכן עורך שנמצא במצב זרימה אינו שולח לאחרים שום שינוי, והשינויים שהם שולחים ממשיכים לחול עליו כל עוד הוא שם.
החריג היחיד הוא יציאה ממצב זרימה לכרטיסייה אחרת: החלפת הכרטיסייה הזו נשלחת כמו כל החלפה אחרת.
לחצן הקישור החיצוני בכרטיס, תוצאה של החיפוש המהיר שנבחרה אחרי #, @ או :, ופתיחת הסייר מעבירים כולם ממצב זרימה אל כרטיסיית 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 של קשר, ומסמך שנשמר ממנו אינו כולל אף אחד מהם. קשר שעורך ישן יותר מוסיף מגיע בלעדיהם, ולכן שניהם מתחילים ב-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.
הטבלה שבפוקוס והתא הפעיל, הטבלאות, הפתקים וקבוצות הטבלאות שנבחרו, ומלבן הבחירה שגוררים על בד הציור נשלחים לעורכים האחרים ומוצגים בהם.
כל משתמש מקבל צבע משלו, כך שניתן להבחין ביניהם: מסגרת סביב הטבלה שבפוקוס, קו תחתון מתחת לתא הפעיל, טבעת סביב הטבלאות, הפתקים וקבוצות הטבלאות שנבחרו, ומלבן מקווקו שמייצג את מלבן הבחירה.
נוכחות שמגיעה ממשתמשים אחרים מוצגת תמיד על בד הציור של המשתמש, בין אם האפשרות הזו מופעלת ובין אם לא.
במצב זרימה של כרטיסיית ויזואליזציה, הפוקוס והבחירה של משתמשים אחרים מוצגים על כרטיסי הטבלאות, אבל מלבני הבחירה שלהם אינם מוצגים. הטבלאות שבוחרים שם עדיין נשלחות; מלבן הבחירה שגוררים שם אינו נשלח.
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 המתודה קיימת בטיפוס אך אינה עושה דבר.
הזזת טבלה, פתק או קבוצת טבלאות, שינוי הצבע שלהם, שינוי גודל של פתק, וגלילה או זום של התצוגה מגיעים כל אחד כזרם של שינויים קטנים. שינוי גודל של קבוצת טבלאות אינו מגיע כך: הוא נשלח פעם אחת, כשהגרירה מסתיימת.
המאגר אוסף כל זרם לאצווה אחת ומעכב אותה עד שהזרם שקט במשך 200ms. הוא עושה זאת בשני שלבים, כך שהאצווה יוצאת כ-400ms אחרי השינוי האחרון.
קריאה מסיימת את ההמתנה הזו בשני השלבים. כל עוד המאגר מחובר, מנוי מקבל את האצווה לפני שהקריאה חוזרת. קריאה כשאין דבר מעוכב אינה שולחת דבר.
המתודה destroy, של המאגר עצמו או של העורך, מוחקת אצווה שעדיין מעוכבת, ולכן יש לקרוא קודם ל-flushStreamBuffers כשמשחררים מאגר והאחרים אמורים לשמור את השינוי האחרון, כמו כשכרטיסייה נסגרת.
sharedStore.flushStreamBuffers();
sharedStore.destroy();
destroy
משמידה לחלוטין את מופע ה-sharedStore.
העורך עצמו אינו מושפע, כך שאחרי עזיבת סשן עדיין ניתן לערוך את המסמך.
רכיבי המעקב אחר העכבר והפוקוס נעצרים ברגע שה-sharedStore האחרון שהעורך מסר מושמד.
אצוות זרם שהמאגר עדיין מעכב נמחקת יחד איתו; כדי לשלוח אותה, יש לקרוא תחילה ל-flushStreamBuffers.
sharedStore.destroy();