diff --git a/packages/front-end/components/Experiment/EditDOMMutationsModal.tsx b/packages/front-end/components/Experiment/EditDOMMutationsModal.tsx new file mode 100644 index 00000000000..4aaba1cf970 --- /dev/null +++ b/packages/front-end/components/Experiment/EditDOMMutationsModal.tsx @@ -0,0 +1,130 @@ +import { VisualChange } from "back-end/types/visual-changeset"; +import { FC, useCallback, useState } from "react"; +import Code from "@/components/SyntaxHighlighting/Code"; +import Modal from "../Modal"; + +const EditDOMMutatonsModal: FC<{ + visualChange: VisualChange; + close: () => void; + onSave: (newVisualChange: VisualChange) => void; +}> = ({ close, visualChange, onSave }) => { + const [newVisualChange, setNewVisualChange] = useState( + visualChange + ); + + const deleteCustomJS = useCallback(() => { + setNewVisualChange({ + ...newVisualChange, + js: "", + }); + }, [newVisualChange, setNewVisualChange]); + + const deleteGlobalCSS = useCallback(() => { + setNewVisualChange({ + ...newVisualChange, + css: "", + }); + }, [newVisualChange, setNewVisualChange]); + + const deleteDOMMutation = useCallback( + (index: number) => { + setNewVisualChange({ + ...newVisualChange, + domMutations: newVisualChange.domMutations.filter( + (_m, i) => i !== index + ), + }); + }, + [newVisualChange, setNewVisualChange] + ); + + const onSubmit = () => { + onSave(newVisualChange); + }; + + return ( + +
+
+

+ Global CSS + {newVisualChange.css ? ( + + + delete + + + ) : null} +

+ {newVisualChange.css ? ( + + ) : ( +
(None)
+ )} +
+ +
+

+ Custom JS + {newVisualChange.js ? ( + + + delete + + + ) : null} +

+ {newVisualChange.js ? ( + + ) : ( +
(None)
+ )} +
+ +
+

DOM Mutations

+ + {newVisualChange.domMutations.length ? ( + newVisualChange.domMutations.map((m, i) => ( + + )) + ) : ( +
(None)
+ )} +
+
+
+ ); +}; + +export default EditDOMMutatonsModal; diff --git a/packages/front-end/components/Experiment/VariationsTable.tsx b/packages/front-end/components/Experiment/VariationsTable.tsx index 960242bc81b..b8fee58b91b 100644 --- a/packages/front-end/components/Experiment/VariationsTable.tsx +++ b/packages/front-end/components/Experiment/VariationsTable.tsx @@ -5,11 +5,12 @@ import { Variation, } from "back-end/types/experiment"; import { + VisualChange, VisualChangesetInterface, VisualChangesetURLPattern, } from "back-end/types/visual-changeset"; -import React, { FC, Fragment, useState } from "react"; -import { FaPlusCircle, FaTimesCircle } from "react-icons/fa"; +import React, { FC, Fragment, useCallback, useState } from "react"; +import { FaPencilAlt, FaPlusCircle, FaTimesCircle } from "react-icons/fa"; import { useAuth } from "@/services/auth"; import { useUser } from "@/services/UserContext"; import DeleteButton from "@/components/DeleteButton/DeleteButton"; @@ -22,6 +23,7 @@ import ScreenshotUpload from "../EditExperiment/ScreenshotUpload"; import { GBEdit } from "../Icons"; import OpenVisualEditorLink from "../OpenVisualEditorLink"; import VisualChangesetModal from "./VisualChangesetModal"; +import EditDOMMutatonsModal from "./EditDOMMutationsModal"; interface Props { experiment: ExperimentInterfaceStringDates; @@ -130,10 +132,57 @@ const VariationsTable: FC = ({ setEditingVisualChangeset, ] = useState(null); + const [editingVisualChange, setEditingVisualChange] = useState<{ + visualChangeset: VisualChangesetInterface; + visualChange: VisualChange; + visualChangeIndex: number; + } | null>(null); + const hasDescriptions = variations.some((v) => !!v.description?.trim()); const hasUniqueIDs = variations.some((v, i) => v.key !== i + ""); const hasLegacyVisualChanges = variations.some((v) => isLegacyVariation(v)); + const deleteVisualChangeset = useCallback( + async (id: string) => { + await apiCall(`/visual-changesets/${id}`, { + method: "DELETE", + }); + mutate(); + track("Delete visual changeset", { + source: "visual-editor-ui", + }); + }, + [apiCall, mutate] + ); + + const updateVisualChange = useCallback( + async ({ + visualChangeset, + visualChange, + index, + }: { + visualChangeset: VisualChangesetInterface; + visualChange: VisualChange; + index: number; + }) => { + const newVisualChangeset: VisualChangesetInterface = { + ...visualChangeset, + visualChanges: visualChangeset.visualChanges.map((c, i) => + i === index ? visualChange : c + ), + }; + await apiCall(`/visual-changesets/${visualChangeset.id}`, { + method: "PUT", + body: JSON.stringify(newVisualChangeset), + }); + mutate(); + track("Delete visual changeset", { + source: "visual-editor-ui", + }); + }, + [apiCall, mutate] + ); + return (
= ({ )} { - await apiCall(`/visual-changesets/${vc.id}`, { - method: "DELETE", - }); - mutate(); - track("Delete visual changeset", { - source: "visual-editor-ui", - }); - }} + onClick={() => deleteVisualChangeset(vc.id)} displayName="Visual Changes" />
@@ -374,6 +415,19 @@ const VariationsTable: FC = ({
@@ -446,6 +500,20 @@ const VariationsTable: FC = ({
)} + + {editingVisualChange ? ( + setEditingVisualChange(null)} + onSave={(newVisualChange) => + updateVisualChange({ + index: editingVisualChange.visualChangeIndex, + visualChange: newVisualChange, + visualChangeset: editingVisualChange.visualChangeset, + }) + } + /> + ) : null}
); }; diff --git a/packages/front-end/components/OpenVisualEditorLink.tsx b/packages/front-end/components/OpenVisualEditorLink.tsx index 3ccb59f2852..b4adc269ce8 100644 --- a/packages/front-end/components/OpenVisualEditorLink.tsx +++ b/packages/front-end/components/OpenVisualEditorLink.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo, useState } from "react"; +import { FC, useCallback, useMemo, useState } from "react"; import { FaExternalLinkAlt } from "react-icons/fa"; import { getApiHost } from "@/services/env"; import track from "@/services/track"; @@ -35,6 +35,14 @@ const OpenVisualEditorLink: FC<{ }); }, [visualEditorUrl, id, changeIndex, apiHost]); + const navigate = useCallback(() => { + track("Open visual editor", { + source: "visual-editor-ui", + status: "success", + }); + window.location.href = url; + }, [url]); + return ( <> @@ -120,8 +124,14 @@ const OpenVisualEditorLink: FC<{ } > {isChromeBrowser ? ( - `You'll need to install the GrowthBook DevTools Chrome extension - to use the visual editor.` + <> + You'll need to install the GrowthBook DevTools Chrome + extension to use the visual editor.{" "} + + Click here to proceed anyway + + . + ) : ( <> The Visual Editor is currently only supported in Chrome. We are