72 lines
2.8 KiB
TypeScript
72 lines
2.8 KiB
TypeScript
import { get, set } from "lodash";
|
|
import { SequenceBodyItem, uuid } from "farmbot/dist";
|
|
import {
|
|
Traversable,
|
|
} from "../sequences/locals_list/sanitize_nodes";
|
|
|
|
/** HISTORICAL NOTES:
|
|
* This file is the result of some very subtle bugs relating to dynamic
|
|
* children in React components on the sequence editor page. Simply put, the
|
|
* sequence editor needs a way to uniquely identify each sequence step when
|
|
* rendering a sequence.
|
|
*
|
|
* PROBLEM:
|
|
* - React needs a unique `key` prop when iterating over UI list elements.
|
|
* - The only unique key on a sequence step is its index within the array.
|
|
* - An array index is not adequate for tracking highly dynamic content changes
|
|
* such as changes seen within the sequence editor.
|
|
* - If you *do* use only the array index as a `key` prop in the editor,
|
|
* the UI may become out of sync with the underlying data.
|
|
* - The JSON returned from the server is not adequate for uniquely tracking
|
|
* each step in this situation.
|
|
* - Changing the underlying structure of CeleryScript globally to fix a problem
|
|
* seen only in the sequence editor is a not-so-great idea.
|
|
*
|
|
* SOLUTION:
|
|
* To get around this, we sneakily add a `uuid` property to the all sequence
|
|
* steps. They are ignored by the API. `step.uuid` can be used anywhere a
|
|
* tracking `key={}` is required when iterating over sequence steps in the UI.
|
|
*
|
|
* RATIONALE:
|
|
* We could change the way CeleryScript is structured globally, but that has
|
|
* no usefulness outside of the sequence editor UI. Also, retroactively
|
|
* updating sequence steps already in the database can be error prone and time
|
|
* consuming. I would rather put a hack in one place (here) rather than force
|
|
* all other parts of the product to support it.
|
|
*
|
|
* OTHER IMPORTANT THINGS:
|
|
* My goal is to keep as much UUID tagging logic as possible in this file
|
|
* only. When a more elegant solution appears, it will be easier to remove.
|
|
* Please *AVOID MOVING THESE FUNCTIONS AND INTERFACES INTO SEPARATE FILES*.
|
|
*
|
|
* FURTHER READING:
|
|
* https://facebook.github.io/react/docs/lists-and-keys.html
|
|
*
|
|
* -RC 6-Aug-17
|
|
*/
|
|
|
|
/** Type alias for the data type used to tag steps.
|
|
* Currently `string`. Formerly `number`. */
|
|
export type StepTag = string;
|
|
|
|
/** Property name where a unique ID is stored in a step. */
|
|
const TAG_PROP = "uuid";
|
|
|
|
export const maybeTagStep =
|
|
(t: Traversable) => !get(t, TAG_PROP) && forceSetStepTag(t);
|
|
|
|
export const forceSetStepTag = <T extends Traversable>(node: T): T => {
|
|
set(node, TAG_PROP, uuid());
|
|
return node;
|
|
};
|
|
|
|
/** VERY IMPORTANT FUNCTION.
|
|
* SEE HEADER AT TOP OF FILE.
|
|
* Retrieves tag from a step object. Assumes that all steps have a tag.
|
|
* If no tag is found, crashes. */
|
|
export function getStepTag(i: SequenceBodyItem): StepTag {
|
|
const tag = get(i, TAG_PROP, "");
|
|
if (tag) { return tag; }
|
|
throw new Error("No tag on step: " + i.kind);
|
|
}
|