From 69beeace19dd1c4e107e791cdb06894290d47c6c Mon Sep 17 00:00:00 2001 From: Peter Szerzo Date: Tue, 2 Jul 2024 15:43:14 +0200 Subject: [PATCH] Appear event handling --- packages/journey-manager/src/index.ts | 92 +++++++++++++++---- .../website/src/components/Prototyping.tsx | 31 ++++++- 2 files changed, 100 insertions(+), 23 deletions(-) diff --git a/packages/journey-manager/src/index.ts b/packages/journey-manager/src/index.ts index 0e468497..d25c239d 100644 --- a/packages/journey-manager/src/index.ts +++ b/packages/journey-manager/src/index.ts @@ -70,7 +70,7 @@ interface LoadStep { } /** - * Click step + * Step with additional query */ export interface StepWithQuery { /** @@ -91,6 +91,16 @@ export interface StepWithQuery { urlCondition?: UrlCondition; } +/** + * Step with query and found elements + */ +export type StepWithQueryAndElements = StepWithQuery & { + /** + * Elements found + */ + elements?: HTMLElement[]; +}; + const debounce = (func: () => void, timeout = 300): (() => void) => { let timer: NodeJS.Timer | null = null; return () => { @@ -126,6 +136,29 @@ const matchesUrlCondition = (urlCondition: UrlCondition): boolean => { return false; }; +const withElements = async ( + steps: Array, +): Promise => { + const targets = await Promise.all( + steps + .filter( + ({ urlCondition }) => + urlCondition == null || matchesUrlCondition(urlCondition), + ) + .map(async (step) => { + try { + return { + ...step, + elements: await find(step.query), + }; + } catch (e) { + return step; + } + }), + ); + return targets; +}; + const localStorageKey = (conversationId: string): string => `jb-triggered-steps-${conversationId}`; @@ -371,25 +404,24 @@ export const run = async (props: RunProps): Promise => { [], ); + const appearSteps: StepWithQuery[] = Object.entries(triggers).reduce( + (prev: StepWithQuery[], [stepId, trigger]: [StepId, Trigger]) => { + if (trigger.event === "appear" && trigger.query != null) { + const newEntry: StepWithQuery = { + stepId, + query: decode(trigger.query), + urlCondition: trigger.urlCondition, + once: trigger.once, + }; + return [...prev, newEntry]; + } + return prev; + }, + [], + ); + const handleGlobalClickForAnnotations = async (ev: any): Promise => { - const targets = await Promise.all( - clickSteps - .filter( - ({ urlCondition }) => - urlCondition == null || matchesUrlCondition(urlCondition), - ) - .map(async ({ stepId, query }) => { - try { - return { - stepId, - query, - elements: await find(query), - }; - } catch (e) { - return { stepId, query }; - } - }), - ); + const targets = await withElements(clickSteps); const node = ev.target; const clickStep: | (StepWithQuery & { @@ -493,7 +525,27 @@ export const run = async (props: RunProps): Promise => { * Change detection */ - const documentObserver = new MutationObserver(() => { + const documentObserver = new MutationObserver((mutations) => { + // If any of the added nodes are inside matches on appear events, trigger those events + mutations.forEach(async (mutation) => { + const targets = await withElements(appearSteps); + targets.forEach(({ stepId, once, elements }) => { + if ( + (elements ?? []).some((element) => { + if ( + [...mutation.addedNodes].some((addedNode) => { + if (element.contains(addedNode)) { + sendStep(stepId, once ?? false); + } + }) + ) { + } + return mutation.addedNodes; + }) + ) { + } + }); + }); debouncedSetHighlights(); // If the document changed for any reason (click, popstate event etc.), check if the URL also changed // If it did, handle page load events diff --git a/packages/website/src/components/Prototyping.tsx b/packages/website/src/components/Prototyping.tsx index 28a2e604..2eaa1666 100644 --- a/packages/website/src/components/Prototyping.tsx +++ b/packages/website/src/components/Prototyping.tsx @@ -1,4 +1,4 @@ -import { type FC, useRef, useEffect } from "react"; +import { type FC, useRef, useEffect, useState } from "react"; const runJourneyManager = async (): Promise => { const { run } = await import("@nlxai/journey-manager"); @@ -18,7 +18,16 @@ const runJourneyManager = async (): Promise => { fontFamily: "'Neue Haas Grotesk'", }, }, - triggers: {}, + triggers: { + "abcd-1234": { + event: "appear", + query: { + parent: null, + name: "QuerySelector", + target: "#error", + }, + }, + }, }); return null; }; @@ -26,6 +35,8 @@ const runJourneyManager = async (): Promise => { export const Prototyping: FC = () => { const isRun = useRef(false); + const [showError, setShowError] = useState(false); + useEffect(() => { if (isRun.current) { return; @@ -37,5 +48,19 @@ export const Prototyping: FC = () => { }); }, []); - return

Hello

; + useEffect(() => { + const timeout = setTimeout(() => { + setShowError(true); + }, 3000); + return () => { + clearTimeout(timeout); + }; + }, [setShowError]); + + return ( +
+

Hello

+ {showError ?
Error
: null} +
+ ); };