Salt la conținutul principal

Stocare la distanță

Trimiterea întregii stări a editorului la fiecare modificare este ineficientă.
Pentru acest caz, editorul oferă un API pentru replicarea datelor în timp real.

Instalare​

npm install @dineug/erd-editor

Store-ul de replicare se află la subcalea @dineug/erd-editor/engine.js, nu la rădăcina pachetului.
Acest punct de intrare nu accesează DOM-ul, așa că rulează într-un Web Worker sau oriunde altundeva în afara firului principal.
Importarea rădăcinii pachetului înregistrează în schimb elementul personalizat <erd-editor>, care are nevoie de un document.

Utilizare​

type ReplicationStore = {
readonly value: string;
readonly runtimeValue: string;
on: (
listeners: Partial<{ change: (change: ReplicationChange) => void }>
) => Unsubscribe;
setInitialValue: (value: string) => void;
dispatch: (actions: Array<AnyAction> | AnyAction) => void;
dispatchSync: (actions: Array<AnyAction> | AnyAction) => void;
destroy: () => void;
};

type ReplicationChange = {
value: string;
changed: boolean;
};

type InjectEngineContext = {
toWidth: (text: string) => number;
};

type CreateReplicationStore = (
context: InjectEngineContext
) => ReplicationStore;

// example
import { createReplicationStore } from '@dineug/erd-editor/engine.js';

const replicationStore = createReplicationStore({
toWidth: text => text.length * 10,
});

Replicarea unui editor în funcțiune​

Acțiunile provin din store-ul partajat al unui editor în funcțiune.
Consultați Editare colaborativă.

import { createReplicationStore } from '@dineug/erd-editor/engine.js';

const replicationStore = createReplicationStore({ toWidth });
replicationStore.setInitialValue(editor.runtimeValue);

replicationStore.on({
change: ({ value, changed }) => {
if (changed) save(value);
},
});

const sharedStore = editor.getSharedStore();
sharedStore.subscribe(actions => {
replicationStore.dispatch(actions);
});

Doar modificarea traversează granița, niciodată întregul document.
Replica o aplică și serializează rezultatul de partea sa.
Evenimentul change vă transmite acest rezultat ca value, iar changed vă spune când merită să-l scrieți.

Începând cu versiunea 4.0.0, inițializați replica din proprietatea runtimeValue a editorului, ca mai sus, nu din fișierul stocat sau din proprietatea value a editorului.
Fișierului îi lipsesc editările făcute de la salvare, iar proprietății value îi lipsesc entitățile eliminate în sesiune.
Când un editor anulează eliminarea, o replică inițializată fără un tabel eliminat îl readuce fără nume și fără coloane și îl salvează așa.

InjectEngineContext​

toWidth​

Măsoară un șir de caractere în pixeli.
Store-ul nu are DOM, deci nu poate măsura singur textul, și recalculează lățimile coloanelor cu această funcție de fiecare dată când se modifică un nume, un tip de date, o valoare implicită sau un comentariu.
De asemenea, măsoară din nou fiecare tabel și fiecare coloană ori de câte ori metoda setInitialValue încarcă un document.
Începând cu versiunea 4.0.0, lățimile sunt păstrate doar în runtimeValue, niciodată în value.
Astfel, un document salvat de această versiune, citit din nou, produce octet cu octet aceeași valoare pentru value, oriunde ar fi fost salvat și oricum s-ar fi măsurat textul acolo.

Măsurați așa cum măsoară editorul: 400 12px în lista de fonturi a editorului, cu rotunjire, plus o spațiere interioară de 2 px.
Altfel, lățimile din runtimeValue se abat de la cele de pe ecran.

const TEXT_PADDING = 2;
const FONT =
"400 12px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, " +
"'Helvetica Neue', 'Open Sans', system-ui, sans-serif, " +
"'Apple Color Emoji', 'Segoe UI Emoji'";

let context = null;

function getContext() {
if (context) return context;

try {
context = new OffscreenCanvas(0, 0).getContext('2d');
if (context) context.font = FONT;
} catch {
// no canvas available
}

return context;
}

function toWidth(text) {
const context = getContext();
const width = context ? context.measureText(text).width : text.length * 10;

return Math.round(width) + TEXT_PADDING;
}

const replicationStore = createReplicationStore({ toWidth });

Expresia text.length * 10 este varianta de rezervă atunci când nu este disponibil niciun canvas.
Este suficientă pentru un store al cărui document este doar stocat, niciodată redat.

ReplicationStore​

value getter​

Serializează starea curentă a editorului.
Returnează un șir de caractere JSON, gata de stocat ca atare – nu un obiect parsat.

const data = replicationStore.value;

Începând cu versiunea 4.0.0, proprietatea value este forma de stocare: documentul așa cum îl conține un fișier, același text pe care îl returnează proprietatea value a elementului.
Conține doar entitățile existente, într-o ordine fixă, fără cele eliminate, fără ordinea de suprapunere și fără nimic din ce o încărcare recalculează, de exemplu lățimile pe care le măsoară funcția toWidth.
Textul are o indentare de două spații și se termină cu un caracter de linie nouă, astfel încât același document produce mereu aceiași octeți.
Consultați Forma de stocare și valoarea de execuție pentru a vedea ce conține.

Fiecare setare a cărei blocare este activată se scrie cu valoarea la care a fost blocată, iar orice altă setare, așa cum este în acel moment.
Blocarea Viewport acoperă împreună originea vizualizării și nivelul de zoom, iar fila, limbajul generatorului de cod, ambele stiluri de denumire și tipul de delimitator au fiecare propria blocare.
Blocările înlocuiesc câmpul ignoreSaveSettings al documentului, care nu mai este nici citit, nici scris începând cu versiunea 4.0.0.
Înainte de versiunea 4.0.0, proprietatea value era întregul document așa cum îl păstra store-ul, cu biții de derulare și de zoom ai acelui câmp aplicați.

runtimeValue getter​

Începând cu versiunea 4.0.0, serializează documentul așa cum îl păstrează replica, inclusiv entitățile eliminate și tot ce recalculează o încărcare.
Și aici, fiecare setare blocată se scrie cu valoarea sa blocată.

const snapshot = replicationStore.runtimeValue;

Folosiți valoarea de execuție pentru a inițializa un editor sau un alt store care se alătură mai târziu, prin metoda setInitialValue a acestuia; consultați Editare colaborativă.
Nu o stocați niciodată ca fișier: conține entitățile eliminate, iar JSON Schema publicată o respinge.
Datorită acestor entități eliminate, o anulare a unei eliminări, trimisă ulterior, readuce entitatea întreagă în store-ul inițializat din această valoare.
Ele rămân până la următoarea deschidere a documentului din fișierul său, a cărui formă le omite.

setInitialValue​

Încarcă starea editorului stocată anterior.

replicationStore.setInitialValue('json...');

Acceptă forma de stocare, o valoare de execuție sau un document salvat de o versiune anterioară, inclusiv un document v2.
Un șir de caractere gol sau orice altceva decât un șir de caractere încarcă un document nou și gol, în loc să genereze o eroare.
Un store care nu a primit niciodată o valoare pornește cu același document nou.
Începând cu versiunea 4.0.0, acest document are toate blocările activate, așa că proprietatea sa value păstrează vizualizarea, fila, limbajul generatorului de cod, stilurile de denumire și tipul de delimitator cu care a pornit, în timp ce editoarele pe care le replică derulează, modifică zoomul, comută filele sau schimbă acele setări. În versiunea 3.10.0, nu salva nici derularea, nici zoomul, iar înainte de aceasta le salva pe amândouă.
Un document stocat păstrează blocările pe care le specifică.
Unul care nu specifică niciuna, adică orice document salvat înainte de versiunea 4.0.0, inclusiv un document v2, se încarcă cu toate blocările activate: originea vizualizării la 0, 0, nivelul de zoom la 1 și fila la ERD, indiferent ce a salvat, iar limbajul generatorului de cod, stilurile de denumire și tipul de delimitator așa cum le-a salvat.
Începând cu versiunea 4.0.0, metoda setInitialValue elimină și registrele de colaborare păstrate pentru documentul anterior, la fel ca metoda setInitialValue a elementului.
Un text care nu este JSON, de exemplu un fișier rămas cu marcaje de conflict de îmbinare, nu generează nici el o eroare: eroarea de parsare este doar înregistrată în consolă, documentul este golit, iar setările rămân cum erau.

Începând cu versiunea 4.0.0, încărcarea nu rulează colectarea gunoiului, așa că entitățile eliminate rămân în runtimeValue.
Înainte, elimina entitățile care nu mai apăreau în doc și nu fuseseră modificate de patru zile sau mai mult.

Rescrierile făcute de încărcare se încheie înainte ca metoda setInitialValue să returneze: marcajele de cheie de pe coloane, deduse din opțiunile fiecărei coloane și din relații; lățimea fiecărui tabel și a fiecărei coloane, măsurată din nou cu toWidth; punctele în care fiecare conector își întâlnește tabelele; și indicatorul de relație identificatoare al fiecărei relații și simbolul de la capătul ei dinspre tabelul părinte.
Niciuna dintre ele nu face parte din value, dar valoarea replicationStore.runtimeValue citită imediat după apel le conține deja pe toate.
O acțiune care sosește imediat după încărcare, de exemplu o derulare reluată, este comparată cu documentul încărcat, așa că încărcarea însăși nu este niciodată raportată ca changed.

Nici încărcarea, nici vreuna dintre aceste rescrieri nu emite evenimentul change.
Citiți dumneavoastră replicationStore.value după ce metoda setInitialValue returnează, dacă doriți ca documentul încărcat să fie scris înapoi în forma de stocare.

on@change​

Se abonează la modificările stării editorului.

const unsubscribe = replicationStore.on({
change: ({ value, changed }) => {
if (changed) {
// save value...
}
},
});

Evenimentul change este amânat (debounce) cu 200 ms, așa că o rafală de acțiuni ajunge ca o singură notificare.
Se declanșează doar pentru acțiuni de un tip care poate modifica documentul, plus acțiunea settings.changeIgnoreSaveSettings pe care un editor mai vechi o poate trimite în continuare (consultați dispatch, dispatchSync).
O acțiune de prezență retransmisă sau acțiunea editor.mergeLWW nu îl declanșează niciodată.
Apelați funcția returnată pentru a vă dezabona.

Începând cu versiunea 3.10.0, ascultătorul primește un obiect de tipul ReplicationChange, tip pe care subcalea @dineug/erd-editor/engine.js îl exportă alături de ReplicationStore.
Înainte de versiunea 3.10.0, nu primea nimic, iar un ascultător care ignoră argumentul funcționează în continuare.
Proprietatea value este documentul serializat în forma de stocare, același șir pe care îl returnează replicationStore.value în acel moment, deci nu este nevoie să-l serializați din nou.
Proprietatea changed este false când acțiunile de la evenimentul change anterior sau de la încărcare au lăsat value octet cu octet așa cum era.

O modificare pe care o blocare o lasă în afara proprietății value declanșează totuși evenimentul change, cu changed setat la false, așa că scrieți doar când changed este true.
Este vorba de o derulare sau un zoom cât timp setarea Viewport este blocată, de o comutare de filă cât timp setarea Canvas Type este blocată și de o modificare a limbajului generatorului de cod, a unui stil de denumire sau a tipului de delimitator cât timp propria sa blocare este activată.
Evenimentul change al elementului omite o astfel de modificare, dar replica o raportează totuși.
Toate blocările sunt activate într-un document nou și într-unul salvat înainte de versiunea 4.0.0.
Cu setarea Viewport deblocată, o derulare sau un zoom modifică value, iar cu setarea Canvas Type deblocată, la fel face și deschiderea unei alte file a editorului.
Blocarea sau deblocarea unei setări modifică de asemenea value.

Începând cu versiunea 4.0.0, proprietatea changed compară forma de stocare, care nu conține nici entitățile eliminate, nici ordinea de suprapunere.
Astfel, un tabel adăugat și eliminat din nou în cadrul aceluiași eveniment change, o editare care ajunge la o entitate pe care alt colaborator a eliminat-o și o încărcare retransmisă care schimbă doar ordinea de suprapunere se soldează toate, de asemenea, cu changed setat la false.

Proprietatea changed nu face niciodată comparația cu fișierul dumneavoastră stocat, care poate diferi deja de value imediat ce se încarcă: așa se întâmplă cu un fișier salvat de o versiune anterioară, inclusiv un fișier v2, precum și cu un fișier care începe cu un marcaj de ordine a octeților și cu unul editat manual.
O comparare a octeților cu un astfel de fișier ar considera editare chiar și o derulare pe care o blocare o omite.
Primul eveniment change cu changed setat la true vă returnează un astfel de fișier în forma de stocare, așa că scrierea lui rescrie fișierul o singură dată.
Un fișier salvat de această versiune nu diferă, oriunde ar fi fost salvat, așa cum explică secțiunea toWidth.
Dacă apelul setInitialValue intervine cât timp un eveniment change este încă în așteptare și nimic nu este trimis înainte de momentul acestuia, acel eveniment change sosește totuși, cu proprietatea value a documentului încărcat și cu changed setat la false.

dispatch, dispatchSync​

Aplică modificările unui editor la distanță în store-ul replicationStore.

replicationStore.dispatch(actions); // async
replicationStore.dispatchSync(actions); // sync

Metoda dispatch amână aplicarea într-un microtask, iar metoda dispatchSync aplică lotul imediat.

Puteți transmite nefiltrat fluxul unui store partajat.
Ambele metode păstrează doar acțiunile de un tip care poate modifica documentul, plus cele două descrise la sfârșitul acestei secțiuni, și le elimină pe celelalte, astfel încât prezența pe care o transportă o sesiune colaborativă – cursorul mouse-ului, focusul, selecția și caseta de selecție ale fiecărui utilizator – trece fără niciun efect.

Începând cu versiunea 3.10.0, acțiunile păstrate includ relationship.changeOnDelete și relationship.changeOnUpdate, iar acțiunea relationship.add transportă câmpurile onDelete și onUpdate.
Un store dintr-o versiune anterioară elimină aceste acțiuni și ignoră aceste câmpuri, așa că pierde acțiunile ON DELETE și ON UPDATE ale unei relații.

Începând cu versiunea 4.0.0, acțiunile păstrate includ și table.changeGroup, șapte dintre cele opt acțiuni tableGroup (tableGroup.add, tableGroup.move, tableGroup.moveTo, tableGroup.remove, tableGroup.resize, tableGroup.changeName și tableGroup.changeColor, toate cu excepția modificării ordinii de suprapunere tableGroup.changeZIndex), relationship.changeColumns, settings.changeLockSettings și settings.changeDDLScript.
Acțiunea settings.changeRelationshipOptimization nu mai există, așa că ambele metode o elimină.
Un store dintr-o versiune anterioară elimină noile acțiuni, așa că pierde grupurile de tabele, blocările, scripturile din fila Schemă SQL și coloanele pe care caseta de dialog Mapare coloane le atribuie unei relații existente.
Rulați replica pe aceeași versiune ca editoarele sau pe una mai nouă.

Versiunea 4.1.0 nu adaugă niciun tip de acțiune și lasă subcalea @dineug/erd-editor/engine.js neschimbată, așa că o replică 4.0.0 păstrează fiecare modificare pe care o trimite un editor 4.1.0, exact ca o replică 4.1.0.
Începând cu versiunea 4.1.0, butoanele Replace și Replace All din Găsire și înlocuire redenumesc de asemenea indexuri și grupuri de tabele, prin acțiunile index.changeName și tableGroup.changeName pe care o replică 4.0.0 le păstrează deja.
Starea proprie a exploratorului nu este trimisă niciodată, iar o comutare de filă sau o derulare pe care o face sosește ca orice altă comutare de filă sau derulare; consultați Editare colaborativă.

Începând cu versiunea 4.0.0, ambele metode aplică și acțiunea editor.mergeLWW, care transportă registrele de colaborare cu care un editor răspunde când un altul se alătură sesiunii, astfel încât replica rezolvă editările simultane la fel ca editoarele.
Aceasta nu modifică niciun octet din value și nu declanșează niciun eveniment change.
Acțiunea settings.changeIgnoreSaveSettings, pe care un editor lansat înainte de versiunea 4.0.0 o poate trimite în continuare, este păstrată, dar nu modifică nimic: declanșează evenimentul change cu changed setat la false.

destroy​

Distruge complet instanța replicationStore.

replicationStore.destroy();

După aceea, store-ul nu mai emite evenimentul change și ignoră trimiterile ulterioare.
Documentul rămâne înghețat la ultima sa valoare, în loc să genereze o eroare.
Creați un store nou pentru a o lua de la capăt.