Zum Inhalt springen

Rückgängig machen und Verlauf ​

graph.history ist eine Aufzeichnung von was die Arbeitsfläche enthält und zeigt – was hineingebracht wurde, was herausgenommen wurde, was verborgen ist – und eine Möglichkeit, etwas davon zurückzunehmen.

Es handelt sich nicht um eine Aufzeichnung aller Mutationen, die die Bibliothek durchführen kann. Die Daten eines Knotens gehören zu Ihrem Backend, daher bietet die Bibliothek nicht an, ein Feld zurückzusetzen, das sie nicht erstellt hat. Wofür es sprechen kann, ist die Zusammensetzung, über die auch ein Analyst nachdenkt: Zwölf Knoten sind angekommen, drei sind gegangen, fünf sind verborgen.

Vokabular, im Folgenden einheitlich verwendet

  • Eintrag: eine umkehrbare Sache, die passiert ist, als eine Zeile im Menü
  • kind: um welche der sechs Eintragsarten es sich handelt
  • span: der zusammenhängende Block vom neuesten Eintrag bis zu dem, auf den Sie geklickt haben
  • persisted: ein Eintrag, den der Verbraucher an ein Backend durchgeschrieben hat
  • versiegelt: eine dauerhafte Löschung – aufgeführt, nie rückgängig gemacht

Die sechs Arten ​

Eine geschlossene Menge. Alles, was die Bibliothek aufzeichnet, gehört dazu, und nichts anderes wird aufgezeichnet.

FreundlichWas es istWie es umgekehrt ist
pivotEine Aufnahme – siehe Pivots & EnrichmentDie Bürgschaft des Laufs entfällt, und alles, wofür sonst nichts bürgt, geht
deleteEine vom Benutzer initiierte Löschung von Knoten oder KantenDie Elemente kommen zurück, Herkunft und alles
visibilityEin dauerhaftes Ein- oder Ausblenden (queryEngine.excludeNode / includeNode)Der andere
createEin von Hand gezeichneter Knoten oder eine KanteDas Element gilt, es sei denn, etwas anderes bürgt dafür
removalEin graph.removeBySource(source)-AnrufWas übrig geblieben ist, kommt zurück, und was geblieben ist, erhält die Bürgschaft der Quelle zurück
groupEine Gruppe, die manuell erstellt, umbenannt oder entfernt wurde (siehe Vereinfachen)Die handgefertigten Gruppen werden wieder so, wie sie waren; Es wird kein Knoten hinzugefügt oder entfernt

Absichtlich nicht erfasst:

  • Eigenschaftsänderungen. Backend-Status, wie oben. onBeforeNodeEditCommit Hier implementieren Sie Ihre eigenen.
  • Filter- und Legendenstatus. Einstellungen mit eigenem Löschen statt Handlungen. Das Filterformular wird bei jedem Tastendruck aktiv angewendet, sodass die Eingabe von domain sechs Einträge verschieben würde.
  • Notizen. Autorisierter Text, der eher einer Bearbeitung als einer Grafikkomposition ähnelt.
  • graph.hideNode(). Es wird gelöscht, wenn die Filter das nächste Mal neu abgeleitet werden, sodass ein Eintrag dafür etwas rückgängig machen würde, das sich bereits selbst zurückgesetzt hat. excludeNode ist die Haut, die überlebt und aufgezeichnet wird.
  • Eine Gruppe öffnen, einen Knoten herausziehen, eine Vereinfachungsregel ändern. Eine Sichtweise, wie die Filter.

Programmatische Mutationen – graph.addNode(), graph.removeNode() – sind ebenfalls keine Einträge. Es gibt nur eine Geste, plus graph.removeBySource(), wodurch eine Anwendung dem Analysten die Aktion „Entfernen, was diese Quelle gebracht hat“ anbietet, und graph.simplify.groupNodes(), renameGroup() und ungroup(), die Aufrufe hinter den Gruppengesten.

Rückgängig ist zusammenhängend ​

Wenn Sie auf die Reihe drei nach unten zielen, werden diese drei rückgängig gemacht, da einmal dataBatchChanged und einmal erneut gerendert wird. Es gibt keine Möglichkeit, einen alten Eintrag allein rückgängig zu machen: Ein Stapel, dessen Mitte herausgezogen werden kann, beschreibt nicht mehr, wie der Graph so entstanden ist.

Für „Dieser Anbieter hat Junk zurückgegeben, werfen Sie ihn weg und behalten Sie den Rest“ verwenden Sie graph.removeBySource(source). Es löscht die Bürgschaft einer Quelle und löscht nur das, wofür nichts anderes bürgt, so dass ein Knoten, der auch bei einem zweiten Durchlauf gefunden wurde, überlebt, ein Anspruch weniger. Es handelt sich um einen Vorwärts-Vorgang, der als neuer Eintrag und nicht als Rücklauf aufgezeichnet wird.

Dieser Eintrag wird immer dann aufgezeichnet, wenn die Quelle für irgendetwas bürgt, selbst wenn jedes Element erhalten bleibt, weil eine andere Quelle ebenfalls dafür bürgt: Die Behauptung ist so oder so weg, und in der Zeile heißt esNichts verließ die Arbeitsfläche. Durch das Rückgängigmachen wird die Bürgschaft jedes Elements genau so wiederhergestellt, wie sie war, sodass der Run-Eintrag darunter immer noch vollständig ist und das nächste Rückgängigmachen diesen Run zurücknimmt.

Redo ist streng linear und wird durch jede neue Aktion geleert. Ein Lauf, der rückgängig gemacht wurde und dann durch spätere Arbeit gescheitert ist, ist verschwunden; Um es zurückzubekommen, muss der Pivot erneut ausgeführt werden.

Von der oberen Leiste ​

Bei jeder der beiden Schaltflächen in der Kopfzeile handelt es sich um eine geteilte Schaltfläche: Das Symbol bewegt sich einmal, die Einfügemarke öffnet den Verlauf. Ctrl+Z und Ctrl+Shift+Z (⌘Z und ⌘⇧Z auf macOS) erreichen die gleichen zwei Schritte ohne die Kopfzeile und fallen zum rückgängig machen des Browser-eigenen Textes, während ein Textfeld den Fokus hat. Beide Schaltflächen befinden sich im full- und light-Modus, in dem sich die Kopfzeile befindet, und UI.history: { enabled: false } entfernt sie, das Dropdown-Menü und die beiden Tastenkombinationen für einen Benutzer, der aus seinem eigenen Chrome heraus „Rückgängig“ fährt – graph.history fährt in beide Richtungen mit der Aufnahme fort.

Das Dropdown-Menü ist eine Liste von beiden Schaltflächen, die neueste oben, mit einer durchgehenden Linie „jetzt“: Die darüber liegenden Zeilen wurden rückgängig gemacht, die Zeilen darunter können noch rückgängig gemacht werden. Klicken Sie auf eine Zeile und die Linie verläuft daran vorbei, was in beiden Richtungen die gleiche Geste ist.

Jede Zeile trägt das Symbol ihrer Art, ihre Beschriftung und wie viele Schritte ein Klick darauf zurücklegt. Diese Nummer ist diejenige, die in der Fußzeile angegeben ist, sodass die Liste und die Fußzeile nicht übereinstimmen können. In der Fußzeile wird der genaue Nettoeffekt angegeben, bevor etwas festgeschrieben wird. Pfeiltasten zielen, Enter fährt, Escape schließt.

Die Arbeitsfläche antwortet auch ​

Wenn Sie den Mauszeiger über eine Zeile ** bewegen, wird die Änderung auf der Arbeitsfläche vorhergesagt**, und es ändert sich nichts anderes: Die Elemente, die der Klick entfernen würde, verschwinden dort, wo sie stehen, die Knoten, die er mit ihnen verstecken würde, und was auch immer er zurückbringen würde, ist umrissen, wo er landen würde – ein Knoten, den der Graph noch nicht enthält, wird als Ring an der Position gezeichnet, die er verlassen hat. Der letzte Teil ist das meiste, was eine Wiederholung zu sagen hat, und der Grund dafür, dass eine Prognose kein Highlight ist: Nichts wird zurückgezogen, um die markierten Elemente hervorzuheben, denn die Frage ist, was sich ändern würde, und nicht, welche Elemente eine Zeile benennt.

Die Umrisse sind keine Vermutungen. Beim Rückgängigmachen wird die Stelle vermerkt, an der sich jedes Element befand, als es von der Arbeitsfläche entfernt wurde, und beim Wiederherstellen wird es wieder dorthin zurückgesetzt. Die Prognose zeigt also, wo der Klick die Dinge landet. Ein Pivot reproduziert die Rohdaten seines Anbieters, die keine Koordinaten enthalten, sodass eine Aufnahme ohne diese verstreut an einer neuen Stelle zurückkommen würde.

preview() gibt das Gleiche zurück wie Daten unter forecast und graph.showForecast(…) / graph.clearForecast() malt es – so erhält die eigene Verlaufs-Benutzeroberfläche eines Verbrauchers oder alles andere, das „Was wäre wenn“ fragen möchte, die Canvas-Sprache kostenlos.

In ein Backend geschriebene Vorgänge ​

Nur der Verbraucher kann wissen, dass ein Vorgang in ein Backend geschrieben wurde, also kann nur der Verbraucher dies sagen. onBeforeNodeCreate, onBeforeEdgeCreate und onBeforeDelete akzeptieren jeweils persisted zu ihrer Entscheidung:

js
callbacks: {
    onBeforeNodeCreate: async (ctx) => {
        const values = await ctx.promptData({ fields: [{ key: 'value', label: 'Value', type: 'text' }] })
        if (!values) return false
        const saved = await myBackend.create(values)
        return { accept: true, id: saved.uuid, data: values, persisted: true }
    },
}

Beim Rückgängigmachen wird kein kompensierender Schreibvorgang ausgeführt. Was mit einem solchen Eintrag geschieht, hängt also davon ab, in welche Richtung der Schreibvorgang verlaufen ist.

Eine beibehaltene Erstellung wird immer noch rückgängig gemacht. Die Arbeitsfläche verliert etwas, das das Backend behält, was durch einen erneuten Abruf rückgängig gemacht wird, sodass der Eintrag mit gespeichert markiert und nicht zurückgehalten wird und die Fußzeile ihn vor dem Klick zählt: Undoes 2 steps · 1 item saved upstream.

Eine dauerhafte Löschung ist versiegelt: Sie wird aufgelistet, weil sie Teil dessen ist, wie die Arbeitsfläche auf diese Weise entstanden ist, aber nie rückgängig gemacht wurde. Eine Wiederherstellung würde einen Knoten zurücksetzen, den das Backend nicht mehr hat, und nichts auf der Arbeitsfläche würde dies danach sagen. Eine Spanne, die eins enthält, geht darüber hinweg, anstatt dort anzuhalten, und sagt dies zuerst: Undoes 2 of 3 · 1 saved item kept.

js
const [newest] = graph.history.entries()
newest.persisted   // the consumer wrote it through
newest.sealed      // …and it was a deletion, so undo will not take it back

Das Überspringen funktioniert, weil jeder Eintrag die spezifischen Elemente benennt, die er berührt, sodass es keine Statusunterschiede gibt, die die Reihenfolge beeinträchtigen könnten. Aus diesem Grund sind die sechs Arten auch geschlossen und ein Verbraucher kann keinen eigenen Eintrag aufzeichnen: Ein beliebiger Rückruf im Stapel könnte die Hälfte einer Spanne auslösen, und der Verlauf hätte keine Möglichkeit, dies herauszufinden.

Eine Einnahme zurücknehmen ​

Durch das Rückgängigmachen einer Aufnahme werden die Kandidaten erneut bereitgestellt, sodass die Zeilen wieder in den Überprüfungsbereich zurückkehren, aus dem sie stammen, der Bereich erneut geöffnet wird, wenn er durch die Aufnahme geschlossen wurde, frühere Ablehnungen weiterhin bestehen bleiben und kein Anbieter aufgerufen wird. Der letzte Teil ist der Punkt: Die Alternative besteht darin, zweitausend Korrelationen über eine ratenbegrenzte API erneut abzurufen, um einen Fehler zu beheben, der vor zwei Sekunden gemacht wurde.

Sie kommen ungeprüft und nicht noch markiert zurück. Beim Zurücklaufen geht es darum, ihn richtig zu durchlaufen, und nicht darum, die gleichen Zwölf mit einem Klick wieder zu landen.

Eine Ausführung, die ohne Überprüfung gelandet ist, wie z. B. ein Ein-Klick-Pivot aus dem Kontextmenü oder ein Anbieter, der autoIngest festlegt, kam aus keinem Bereich, sodass beim Rückgängigmachen nur die Knoten entfernt werden.

Dies geschieht nur, wenn die Aufnahme noch der neueste Eintrag ist. Wenn seitdem irgendetwas passiert ist, werden durch „Rückgängig“ die Knoten entfernt und es wird kein Bereich angezeigt: Ein Bereich, der sich bei späterer Arbeit wiederbelebt, wäre schlimmer als der erneute Abruf.

Was es enthält ​

Dreißig Einträge, die ältesten wurden geräumt. Die Räumung ist verständlich, da sie nicht symmetrisch ist: Sie verliert die Fähigkeit, einen alten Lauf zu wiederherstellen, nicht zu entfernen. Die Herkunft lebt von den Elementen selbst, daher erreicht graph.removeBySource(pivotId) immer noch einen Lauf, den die Geschichte vergessen hat.

Der Verlauf ist nur Sitzung. Es übersteht ein Nachladen nicht. Das Wiederherstellen eines früheren Zustands eines gesamten Diagramms ist eine andere Funktion und nicht diese.

Ich fahre damit ​

js
graph.history.entries()      // what can be undone, newest first — the order the menu renders
graph.history.redoable()     // what has been undone and can be redone, newest first
graph.history.canUndo()
graph.history.canRedo()

graph.history.undo()                  // the newest entry
graph.history.undo(entryId)           // the span from the newest through that entry
graph.history.redo(entryId)           // and back again

graph.history.preview(entryId)        // what that span would do, before doing it
graph.history.on(history => …)        // every change to either direction; returns a disposer
graph.history.clear()                 // forget everything; the canvas is left as it is

Das id ** eines Pivot-Eintrags ist** sein runId, sodass ein PivotRunOutcome das Handle bereits in seine eigene Zeile trägt:

js
const outcome = await graph.pivots.run('correlations', graph.getSelectedNodes())
// …later
graph.history.undo(outcome.runId)

preview(entryId, direction?) ist eine Notiz wert. Es fasst die Einträge nicht zusammen – es spielt die Spanne mit einer Kopie des Zustands des Diagramms ab, und zwar über denselben Code, den der Klick festschreibt, und vergleicht das Ergebnis:

js
const { entries, skipped, canvasOnly, nodes, edges, effect, forecast } = graph.history.preview(entryId)
// entries    the span, newest first
// skipped    the sealed ones inside it, which will be left alone
// canvasOnly the ones it reverses that the backend keeps a copy of
// nodes      the elements it would touch that are on the canvas now
// effect     { nodesRemoved, nodesRestored, edgesRemoved, edgesRestored, nodesHidden, nodesShown }
// forecast   the same change as something to paint: { removing, hiding, touching, arriving }

graph.showForecast(forecast)   // …and take it back down with graph.clearForecast()

Da es eher gespielt als beschrieben wird, kommen die unangenehmen Fälle umsonst heraus: Ein durch ein späteres Einblenden aufgehobenes Verstecken wird auf Null gesetzt und sagt dies, und ein Knoten, für den ein zweiter Pivot ebenfalls bürgt, wird nicht als Verlassen gezählt.

Herkunft und warum das Löschen daran erinnert ​

Jeder Knoten und jede Kante trägt den Satz von Quellen, die dafür bürgen: 'seed' für geladene Daten, die Pivot-ID für alles, was er mitgebracht hat, und 'manual' für alles, was von Hand gezeichnet wurde. Eine Menge und kein Skalar, da zwei überlappende Pivotpunkte auf einem Knoten der Normalfall sind.

Bei einer Löschung muss daher die Garantie jedes Elements berücksichtigt werden, nicht nur seine Daten. Ohne das: Ein Durchlauf fügt zwölf hinzu, der Analyst löscht drei, macht die Löschung rückgängig und die drei kehren als vermisste Waisen zurück; Wenn man den Lauf danach rückgängig macht, bleiben sie auf der Arbeitsfläche zurück, ohne dass es eine Erklärung dafür gibt. Damit bleibt beim Rückgängigmachen des Löschvorgangs und beim anschließenden Ausführen genau das übrig, was zuvor vorhanden war.

Eine wissenswerte Konsequenz bei einem Upgrade: Ein manuell erstellter Knoten meldet ['manual'] von getSources(), nicht ['seed']. Das ist es, was graph.removeBySource('manual') zu handgezeichneten Werken macht und es ermöglicht, dass das Rückgängigmachen einer Schöpfung das Element nur dann entfernt, wenn nichts anderes mehr dafür bürgt.