ویرایش مشارکتی
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
مشترک تغییرات ویرایشگر میشود.
تا زمانی که دستکم یک مشترک وجود نداشته باشد، هیچ چیزی از ویرایشگر خارج نمیشود و تغییراتی که پیش از آن انجام شوند در بافر نگه داشته میشوند.
نخستین subscribe همچنین رجیسترهای همکاری ویرایشگرهای دیگر را، که ویرایشهای همزمان را حلوفصل میکنند، از آنها درخواست میکند تا ویرایشگری که دیرتر میپیوندد این ویرایشها را به همان شیوه حلوفصل کند.
این کار سند را با خود نمیآورد، پس ابتدا ویرایشگری را که دیرتر میپیوندد مقداردهی اولیه کنید.
از نسخهٔ 4.0.0، مخزن هر بار که setInitialValue پس از آن نخستین subscribe سندی را در ویرایشگرش بارگذاری کند، دوباره درخواست میکند.
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();