Skip to content

I used the onPopoverRender function, but it executes after rendering, causing a height change that leads to misalignment #603

@dreamer6680

Description

@dreamer6680

"use client";

import { useEffect, useRef } from "react";
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
import { useRouter, useSearchParams, usePathname } from "next/navigation";
import { createRoot } from "react-dom/client";
import { useGuideStore } from "@/common/store/guideStore";

export function ToolGuide({ onTabChange }: { onTabChange: (tab: string) => void }) {
const driverRef = useRef<ReturnType | null>(null);
const isGuidingRef = useRef(false);
const { needShowProjectGuide, setNeedShowProjectGuide } = useGuideStore();

useEffect(() => {
const shown = localStorage.getItem("intro-shown");
if (shown) return;
if (isGuidingRef.current && driverRef.current) {
return;
}

const initDriver = () => {
  try {
    const checkElementsExist = () => {
      const step1 = document.querySelector("#step-summarization");
      const step2 = document.querySelector("#step-mindmap");
      const step3 = document.querySelector("#step-flownote");
      const step4 = document.querySelector("#step-quizzes");
      const step5 = document.querySelector("#step-flashcards");
      return step1 && step2 && step3 && step4 && step5;
    };

    if (!checkElementsExist()) {
      console.warn("Guide elements not found, skipping guide");
      return;
    }

    const driverObj = driver({
      allowClose: false,
      overlayOpacity: 0,
      stagePadding: 0,
      stageRadius: 8,
      allowKeyboardControl: false,
      disableActiveInteraction: false,
      showButtons: [],
      showProgress: false,
      steps: [
        {
          element: "#step-summarization",
          popover: {
            title: "Summary",
            side: "top",
            align: "start",
          },
        },
        {
          element: "#step-mindmap",
          popover: {
            description: "AI Mindmap for Visualizing Complex Topics CuFlow converts your study materials into clean, layered mindmaps, making relationships intuitive and boosting memory retention.",
            side: "right",
            align: "start",
          },
        },
        {
          element: "#step-flownote",
          popover: {
            description: "FlowNote for Automatic, Intelligent Note-Taking FlowNote captures your learning process and turns it into clean, structured notes—continuously and effortlessly.",
            side: "bottom",
            align: "end",
          },
        },
        {
          element: "#step-quizzes",
          popover: {
            description: "Test understanding with AI-predicted quizzes CuFlow creates grounded practice questions with explanations and difficulty ratings, helping you quickly assess what you know and what needs work.",
            side: "bottom",
            align: "end",
          },
        },
        {
          element: "#step-flashcards",
          popover: {
            description: "",
            side: "bottom",
            align: "end",  
          },
        }
      ],
      onDestroyStarted: () => {
        isGuidingRef.current = false;
      },
      onPopoverRender: (popover, opts) => {
        console.log('popover', popover);
        console.log('opts', opts);
        // 隐藏原本的 title/description
        if (popover.title) popover.title.style.display = "none";
        if (popover.description) popover.description.style.display = "none";
        if(popover.footerButtons) popover.footerButtons.style.display = "none";

        // 清理已有自定义内容
        const existingCustom = popover.wrapper.querySelector(".driver-custom-content");
        const footer = popover.wrapper.querySelector(".driver-custom-footer");
        if (existingCustom) {
          existingCustom.remove();
        }
        if (footer) {
          footer.remove();
        }

        popover.title.remove();
        popover.description.remove();
        popover.footerButtons.remove();
        popover.previousButton.remove();
        popover.nextButton.remove();
        popover.closeButton.remove();
        popover.footer.remove();
        popover.progress.remove();
        
        // 使用 React 组件渲染自定义内容
        const mountPoint = document.createElement("div");
        mountPoint.className = "driver-custom-content";
        popover.wrapper.appendChild(mountPoint);

        const root = createRoot(mountPoint);
        
        // 先让 wrapper 隐藏,避免在重新定位前闪烁
        popover.wrapper.style.opacity = '0';
        
        root.render(
          <div className="w-full h-full bg-white flex flex-col items-center justify-center relative gap-2">
            <span
              className="mt-[40px] w-full justify-start text-slate-900 text-sm font-normal font-Gloria-Hallelujah leading-7"
              style={{ fontFamily: '"Gloria Hallelujah","Helvetica","sans-serif"' }}
            >
              {popover.description.textContent}
            </span>
            <img src="/img/guide/guide.svg" className="w-[127px] h-auto absolute top-[-65px] right-0" alt="" />
            <div className="w-full h-full flex items-center justify-end cursor-pointer">
              <button className="bg-black text-white px-4 py-2 rounded-md"
                onClick={() => {
                  const activeElement = driverRef.current?.getActiveElement();
                  // 根据当前步骤更新 URL
                  if (activeElement?.id === "step-summarization") {
                    onTabChange('mindmap');
                    driverObj.moveNext();
                    // 不调用 moveTo,让 driver 自动继续到下一步
                  } else if (activeElement?.id === "step-mindmap") {
                    onTabChange('flownote');
                    driverObj.moveNext();
                    // 不调用 moveTo,让 driver 自动继续到下一步
                  } else if (activeElement?.id === "step-flownote") {
                    onTabChange('quizzes');
                    driverObj.moveNext();
                    // 不调用 moveTo,让 driver 自动继续到下一步
                  } else if (activeElement?.id === "step-quizzes") {
                    onTabChange('flashcards');
                    driverObj.moveNext();
                    // 不调用 moveTo,让 driver 自动继续到下一步
                  } else if (activeElement?.id === "step-flashcards") {
                    setNeedShowProjectGuide(true);
                    driverObj.destroy();
                  }
                }}>
                Next
              </button>
            </div>
          </div>
        );
        
        // 等待 React 渲染完成后,重新计算位置并显示
        setTimeout(() => {
          // driverRef.current?.refresh();
          popover.wrapper.style.opacity = '1';
        }, 20);
      },
    });

    driverRef.current = driverObj;
    isGuidingRef.current = true; // 标记开始引导

    // 延迟执行,确保 DOM 已渲染
    const timer = setTimeout(() => {
      if (driverRef.current) {
        driverRef.current.drive();
      }
    }, 100);

    return () => {
      clearTimeout(timer);
    };
  } catch (error) {
    console.error("Failed to initialize driver.js:", error);
  }
};

const cleanup = initDriver();

// 清理函数
return () => {
  if (cleanup) cleanup();
  // 注意:不要在这里销毁 driver,因为 URL 变化时会触发清理
  // 只有在组件真正卸载时才销毁
};

}, []); // 移除 searchParams 和 pathname 依赖,只在组件挂载时初始化一次

// 组件卸载时清理
useEffect(() => {
return () => {
if (driverRef.current) {
driverRef.current.destroy();
driverRef.current = null;
isGuidingRef.current = false;
}
};
}, []);

return null;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions