| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- import * as React from "react";
- import * as Mantine from "@mantine/core";
- import * as Icons from "@tabler/icons";
- import {newOnion} from "./single_model";
- import {emptyGraph} from "../d3graph/d3graph";
- import {D3GraphUpdater} from "../d3graph/reducers/onion_graph";
- import {RountangleParser} from "../../parser/rountangle_parser";
- import {Version} from "onion-core";
- import {GraphState} from "onion-core";
- import {PrimitiveDelta} from "onion-core";
- export const undoButtonHelpTextCorr =
- `Navigating to a version in the correspondence model,
- will also navigate to the embedded concrete and abstract syntax model versions.`;
- // Pure function
- // Replays all deltas in a version to compute the graph state of that version.
- function getGraphState(version: Version, listener?): GraphState {
- const graphState = new GraphState();
- for (const d of [...version].reverse()) {
- if (listener !== undefined) {
- graphState.exec(d, listener);
- }
- else {
- graphState.exec(d);
- }
- }
- return graphState;
- }
- export function newCorrespondence({deltaRegistry, generateUUID, versionRegistry}) {
- const {useOnion, ...onion} = newOnion({readonly: true, deltaRegistry, versionRegistry});
- // All versions that are part of this correspondence
- // We need this to know if some CS/AS version has already been parsed/rendered.
- const corrVersions = new Set<Version>([versionRegistry.initialVersion]);
- const isPartOfThisCorrespondence = v => corrVersions.has(v);
- function useCorrespondence(
- {state: {version: csVersion}, reducer: csReducer},
- {state: {version: asVersion}, reducer: asReducer},
- ) {
- // "override" gotoVersion from corr-onion.
- const gotoVersion = (version: Version) => {
- const csVersion = version.embeddings.get("cs")?.version;
- const asVersion = version.embeddings.get("as")?.version;
- if (version === versionRegistry.initialVersion) {
- reducer.gotoVersion(version);
- csReducer.gotoVersion(version);
- asReducer.gotoVersion(version);
- }
- if (csVersion !== undefined && asVersion !== undefined) {
- // user clicked on a correspondence model version
- reducer.gotoVersion(version);
- csReducer.gotoVersion(csVersion);
- asReducer.gotoVersion(asVersion);
- return;
- }
- // if (version.reverseEmbeddings.get("cs").some(isPartOfThisCorrespondence)) {
- // csReducer.gotoVersion(version);
- // return;
- // }
- // if (version.reverseEmbeddings.get("as").some(isPartOfThisCorrespondence)) {
- // asReducer.gotoVersion(version);
- // return;
- // }
- };
- const {state, reducer, components} = useOnion(reducer => ({
- onUndoClicked: gotoVersion,
- onRedoClicked: gotoVersion,
- onVersionClicked: gotoVersion,
- onMerge: (versions: Version[]) => {
- versions.forEach(v => corrVersions.add(v));
- reducer.appendVersions(versions);
- csReducer.appendVersions(versions.map(v => v.embeddings.get("cs")?.version).filter(v => v!==undefined));
- asReducer.appendVersions(versions.map(v => v.embeddings.get("as")?.version).filter(v => v!==undefined));
- }
- }));
- // Helper
- const filterCorrParents = (corrParentVersions: Version[]) => {
- // when parsing/rendering, if one CORR-parent is a parent of another CORR-parent, then drop this one.
- return corrParentVersions.filter(parent => !corrParentVersions.some(child => child !== parent && parent.findDescendant(child) !== undefined));
- }
- // Reducer
- const parseExistingVersion = (csVersion: Version) => {
- for (const [csParentVersion, csTx] of csVersion.parents) {
- const csDeltas = [...csTx.iterPrimitiveDeltas()];
- const description = csTx.description;
- // Recursively parse parent versions, if not parsed yet.
- if (!csParentVersion.reverseEmbeddings.get("cs")?.some(isPartOfThisCorrespondence)) {
- parseExistingVersion(csParentVersion);
- }
- const corrParentVersions = csParentVersion.getReverseEmbeddings("cs").filter(isPartOfThisCorrespondence);
- if (corrParentVersions.length === 0) {
- throw new Error("Assertion failed: CS has a parent, but this parent is not yet part of a CORR version.");
- }
- // Even though parsing is deterministic, it is still possible that the same CS version is part of multiple CORR versions.
- // For instance, when merging at the level of CS, and then rendering the change (with a conflict between the CORR-parents), results in a single CS version embedded in multiple CORR versions.
- const filteredCorrParentVersions = filterCorrParents(corrParentVersions);
- // And if then, there are still multiple CORR versions to choose from, we just pick one:
- // (it would be better to ask the user which one, but whatever)
- for (const corrParentVersion of filteredCorrParentVersions) {
- const asParentVersion = corrParentVersion.getEmbedding("as").version;
- if (asParentVersion === undefined) {
- throw new Error("Assertion failed: CS's parent is part of a CORR version, but that CORR version does not embed an AS version.");
- }
-
- const [csGS, corrGS, asGS] = [csParentVersion, corrParentVersion, asParentVersion].map(v => getGraphState(v));
-
- const parser = new RountangleParser(deltaRegistry, generateUUID);
- const {corrDeltas, asDeltas, csOverrides, asOverrides} = parser.parse(csDeltas, csGS, corrGS, asGS);
-
- const asVersion = asDeltas.length > 0 ? asReducer.createAndGotoNewVersion(asDeltas, "as:"+description, asParentVersion) : asParentVersion;
-
- reducer.appendVersions([csVersion, asVersion]);
-
- const corrVersion = reducer.createAndGotoNewVersion(corrDeltas, "corr:"+description, corrParentVersion,
- () => new Map([
- ["cs", {version: csVersion, overridings: csOverrides}],
- ["as", {version: asVersion, overridings: asOverrides}],
- ]));
-
- corrVersions.add(corrVersion);
-
- }
- }
- };
- const renderExistingVersion = async (asVersion: Version, setManualRendererState) => {
- for (const [asParentVersion, asTx] of asVersion.parents) {
- const asDeltas = [...asTx.iterPrimitiveDeltas()];
- const description = asTx.description;
- const render = () => {
- const corrParentVersions = asParentVersion.getReverseEmbeddings("as").filter(isPartOfThisCorrespondence);
- if (corrParentVersions.length === 0) {
- throw new Error("Assertion failed: AS has a parent, but this parent is not yet part of a CORR version.");
- }
- // AS version may be embedded into multiple CORR versions
- // If one CORR version is a child of another (parent), then we only render based on the child:
- const filteredCorrParentVersions = filterCorrParents(corrParentVersions);
- // And if then, there are still multiple CORR versions to choose from, we just pick one:
- // (it would be better to ask the user which one, but whatever)
- const [corrParentVersion] = filteredCorrParentVersions;
- console.log({corrParentVersion, filteredCorrParentVersions, corrParentVersions});
- const csParentVersion = corrParentVersion.getEmbedding("cs").version;
- if (csParentVersion === undefined) {
- console.log("Cannot parse - no parent CS version");
- return;
- }
- const [csGS, corrGS, asGS] = [csParentVersion, corrParentVersion, asParentVersion].map(v => getGraphState(v));
- const parser = new RountangleParser(deltaRegistry, generateUUID);
- const {corrDeltas, csDeltas, complete, csOverrides, asOverrides} = parser.render(asDeltas, csGS, corrGS, asGS);
- function finishRender({corrDeltas, csDeltas}) {
- const csVersion = csDeltas.length > 0 ? csReducer.createAndGotoNewVersion(csDeltas, "cs:"+description, csParentVersion) : csParentVersion;
- reducer.appendVersions([csVersion, asVersion]);
- const corrVersion = reducer.createAndGotoNewVersion(corrDeltas, "corr:"+description, corrParentVersion,
- () => new Map([
- ["cs", {version: csVersion, overridings: csOverrides}],
- ["as", {version: asVersion, overridings: asOverrides}],
- ]));
- corrVersions.add(corrVersion);
- }
- if (complete) {
- finishRender({corrDeltas, csDeltas});
- return Promise.resolve();
- }
- else {
- function getD3State(version: Version, additionalDeltas: PrimitiveDelta[] = []) {
- let graph = emptyGraph;
- const setGraph = callback => (graph = callback(graph));
- const d3Updater = new D3GraphUpdater(setGraph, 0, 0);
- const graphState = getGraphState(version, d3Updater);
- for (const d of additionalDeltas) {
- graphState.exec(d, d3Updater);
- }
- return {graph, graphState};
- }
- const {graph: asGraph, graphState: asGraphState} = getD3State(asVersion);
- const {graph: csGraph, graphState: csGraphState} = getD3State(csParentVersion, csDeltas);
- const csToAs = new Map();
- const asToCs = new Map();
- for (const corrNode of corrGS.nodes.values()) {
- const csNode = corrNode.outgoing.get("cs");
- if (csNode?.type !== "node") {
- continue; // corrNode is not a correspondence node
- }
- const asNode = corrNode.outgoing.get("as");
- if (asNode?.type !== "node") {
- continue; // corrNode is not a correspondence node
- }
- csToAs.set(csNode.creation.id, asNode.creation.id);
- asToCs.set(asNode.creation.id, csNode.creation.id);
- }
- return new Promise((resolve: (csDeltas: PrimitiveDelta[]) => void, reject: () => void) => {
- setManualRendererState({
- asGraph,
- csGraph,
- asGraphState,
- csGraphState,
- csToAs,
- asToCs,
- asDeltasToRender: asDeltas,
- done: resolve,
- cancel: reject,
- });
- })
- .then((additionalCsDeltas: PrimitiveDelta[]) => {
- finishRender({
- corrDeltas: corrDeltas.concat(additionalCsDeltas),
- csDeltas: csDeltas.concat(additionalCsDeltas),
- });
- })
- .catch()
- .finally(() => setManualRendererState(null));
- }
- }
- if (asParentVersion.getReverseEmbeddings("as").filter(isPartOfThisCorrespondence).length === 0) {
- return renderExistingVersion(asParentVersion, setManualRendererState).then(render);
- }
- else {
- return render();
- }
- }
- };
- // React components
- const getParseButton = (dir: "left"|"right" = "right") => {
- return <Mantine.Button compact
- disabled={(csVersion.reverseEmbeddings.get("cs") || []).some(v => corrVersions.has(v))}
- onClick={() => parseExistingVersion(csVersion)}
- rightIcon={dir === "right" ? <Icons.IconChevronsRight/>: null}
- leftIcon={dir === "left" ? <Icons.IconChevronsLeft/>: null}
- >Parse</Mantine.Button>;
- };
- const getRenderButton = (setManualRendererState, dir: "left"|"right" = "left") => {
- return <Mantine.Button compact
- disabled={(asVersion.reverseEmbeddings.get("as") || []).some(v => corrVersions.has(v))}
- onClick={() => renderExistingVersion(asVersion, setManualRendererState)}
- rightIcon={dir === "right" ? <Icons.IconChevronsRight/>: null}
- leftIcon={dir === "left" ? <Icons.IconChevronsLeft/>: null}
- >Render</Mantine.Button>;
- };
- const getCaptionWithParseButton = (autoParseState, setAutoParseState, dir: "left"|"right" = "right") => {
- const sw = <Mantine.Switch label="Auto"
- labelPosition={dir}
- checked={autoParseState}
- onChange={(event) => setAutoParseState(event.currentTarget.checked)}
- />;
- const button = getParseButton(dir);
- if (dir === "left") {
- return <Mantine.Group>{button}{sw}</Mantine.Group>;
- }
- else {
- return <Mantine.Group>{sw}{button}</Mantine.Group>;
- }
- };
- const getCaptionWithRenderButton = (autoRenderState, setAutoRenderState, setManualRendererState, dir: "left"|"right" = "left") => {
- const sw = <Mantine.Switch label="Auto"
- labelPosition={dir}
- checked={autoRenderState}
- onChange={(event) => setAutoRenderState(event.currentTarget.checked)}
- />
- const button = getRenderButton(setManualRendererState, dir);
- if (dir === "left") {
- return <Mantine.Group>{button}{sw}</Mantine.Group>;
- }
- else {
- return <Mantine.Group>{sw}{button}</Mantine.Group>;
- }
- };
- return {
- state,
- reducer: {
- ... reducer,
- parseExistingVersion,
- renderExistingVersion,
- gotoVersion,
- },
- components: {
- ... components,
- getParseButton,
- getRenderButton,
- getCaptionWithParseButton,
- getCaptionWithRenderButton,
- }
- };
- }
- return {
- ... onion,
- useCorrespondence,
- };
- }
|