enzostvs HF Staff commited on
Commit
980bc0b
·
1 Parent(s): 070e698

keep custom iframe with a throttle

Browse files
components/editor/index.tsx CHANGED
@@ -12,7 +12,6 @@ import {
12
  useUnmount,
13
  useUpdateEffect,
14
  } from "react-use";
15
- import { SandpackPreviewRef } from "@codesandbox/sandpack-react";
16
  import classNames from "classnames";
17
  import { useRouter, useSearchParams } from "next/navigation";
18
 
@@ -47,7 +46,7 @@ export const AppEditor = ({
47
  const router = useRouter();
48
  const deploy = searchParams.get("deploy") === "true";
49
 
50
- const iframeRef = useRef<SandpackPreviewRef>(null);
51
  const preview = useRef<HTMLDivElement>(null);
52
  const editor = useRef<HTMLDivElement>(null);
53
  const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
@@ -356,7 +355,6 @@ export const AppEditor = ({
356
  ref={preview}
357
  device={device}
358
  pages={pages}
359
- currentPage={currentPage}
360
  setCurrentPage={setCurrentPage}
361
  currentTab={currentTab}
362
  isEditableModeEnabled={isEditableModeEnabled}
@@ -386,7 +384,7 @@ export const AppEditor = ({
386
  }}
387
  htmlHistory={htmlHistory}
388
  setPages={setPages}
389
- // iframeRef={iframeRef}
390
  device={device}
391
  setDevice={setDevice}
392
  />
 
12
  useUnmount,
13
  useUpdateEffect,
14
  } from "react-use";
 
15
  import classNames from "classnames";
16
  import { useRouter, useSearchParams } from "next/navigation";
17
 
 
46
  const router = useRouter();
47
  const deploy = searchParams.get("deploy") === "true";
48
 
49
+ const iframeRef = useRef<HTMLIFrameElement | null>(null);
50
  const preview = useRef<HTMLDivElement>(null);
51
  const editor = useRef<HTMLDivElement>(null);
52
  const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
 
355
  ref={preview}
356
  device={device}
357
  pages={pages}
 
358
  setCurrentPage={setCurrentPage}
359
  currentTab={currentTab}
360
  isEditableModeEnabled={isEditableModeEnabled}
 
384
  }}
385
  htmlHistory={htmlHistory}
386
  setPages={setPages}
387
+ iframeRef={iframeRef}
388
  device={device}
389
  setDevice={setDevice}
390
  />
components/editor/preview/index.tsx CHANGED
@@ -3,13 +3,7 @@ import { useUpdateEffect } from "react-use";
3
  import { useMemo, useState } from "react";
4
  import classNames from "classnames";
5
  import { toast } from "sonner";
6
- import {
7
- SandpackLayout,
8
- SandpackPreview,
9
- SandpackPreviewRef,
10
- SandpackProvider,
11
- useSandpack,
12
- } from "@codesandbox/sandpack-react";
13
 
14
  import { cn } from "@/lib/utils";
15
  import { GridPattern } from "@/components/magic-ui/grid-pattern";
@@ -17,7 +11,7 @@ import { htmlTagToText } from "@/lib/html-tag-to-text";
17
  import { Page } from "@/types";
18
 
19
  export const Preview = ({
20
- // html,
21
  isResizing,
22
  isAiWorking,
23
  ref,
@@ -25,87 +19,126 @@ export const Preview = ({
25
  currentTab,
26
  iframeRef,
27
  pages,
28
- currentPage,
29
- // setCurrentPage,
30
  isEditableModeEnabled,
31
- }: // onClickElement,
32
- {
33
  html: string;
34
  isResizing: boolean;
35
  isAiWorking: boolean;
36
  pages: Page[];
37
- currentPage: string;
38
  setCurrentPage: React.Dispatch<React.SetStateAction<string>>;
39
  ref: React.RefObject<HTMLDivElement | null>;
40
- iframeRef?: React.RefObject<SandpackPreviewRef | null>;
41
  device: "desktop" | "mobile";
42
  currentTab: string;
43
  isEditableModeEnabled?: boolean;
44
  onClickElement?: (element: HTMLElement) => void;
45
  }) => {
46
- const [hoveredElement] = useState<HTMLElement | null>(null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- // add event listener to the iframe to track hovered elements
49
- // const handleMouseOver = (event: MouseEvent) => {
50
- // if (iframeRef?.current) {
51
- // const iframeDocument = iframeRef.current.querySelector("iframe");
52
- // if (iframeDocument) {
53
- // const targetElement = event.target as HTMLElement;
54
- // const iframeEl = iframeDocument as HTMLIFrameElement;
55
- // const iframeBody = iframeEl.contentDocument?.body;
56
- // if (hoveredElement !== targetElement && targetElement !== iframeBody) {
57
- // setHoveredElement(targetElement);
58
- // targetElement.classList.add("hovered-element");
59
- // } else {
60
- // return setHoveredElement(null);
61
- // }
62
- // }
63
- // }
64
- // };
65
- // const handleMouseOut = () => {
66
- // setHoveredElement(null);
67
- // };
68
- // const handleClick = (event: MouseEvent) => {
69
- // if (iframeRef?.current) {
70
- // const iframeDocument = iframeRef.current.querySelector("iframe");
71
- // if (iframeDocument) {
72
- // const targetElement = event.target as HTMLElement;
73
- // const iframeEl = iframeDocument as HTMLIFrameElement;
74
- // const iframeBody = iframeEl.contentDocument?.body;
75
- // if (targetElement !== iframeBody) {
76
- // onClickElement?.(targetElement);
77
- // }
78
- // }
79
- // }
80
- // };
81
 
82
- // useUpdateEffect(() => {
83
- // const cleanupListeners = () => {
84
- // if (iframeRef?.current) {
85
- // const iframeDocument = iframeRef.current.querySelector("iframe");
86
- // if (iframeDocument) {
87
- // iframeDocument.removeEventListener("mouseover", handleMouseOver);
88
- // iframeDocument.removeEventListener("mouseout", handleMouseOut);
89
- // iframeDocument.removeEventListener("click", handleClick);
90
- // }
91
- // }
92
- // };
93
 
94
- // if (iframeRef?.current) {
95
- // const iframeDocument = iframeRef.current.querySelector("iframe");
96
- // if (iframeDocument) {
97
- // cleanupListeners();
 
 
 
 
 
 
98
 
99
- // if (isEditableModeEnabled) {
100
- // iframeDocument.addEventListener("mouseover", handleMouseOver);
101
- // iframeDocument.addEventListener("mouseout", handleMouseOut);
102
- // iframeDocument.addEventListener("click", handleClick);
103
- // }
104
- // }
105
- // }
 
 
106
 
107
- // return cleanupListeners;
108
- // }, [iframeRef, isEditableModeEnabled]);
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  const selectedElement = useMemo(() => {
111
  if (!isEditableModeEnabled) return null;
@@ -113,12 +146,7 @@ export const Preview = ({
113
  return hoveredElement;
114
  }, [hoveredElement, isEditableModeEnabled]);
115
 
116
- const formattedPages = useMemo(() => {
117
- return pages.reduce((acc, page) => {
118
- acc[page.path] = page.html;
119
- return acc;
120
- }, {} as Record<string, string>);
121
- }, [pages]);
122
 
123
  return (
124
  <div
@@ -166,11 +194,12 @@ export const Preview = ({
166
  </span>
167
  </div>
168
  )}
169
- <div
170
  id="preview-iframe"
 
171
  title="output"
172
  className={classNames(
173
- "w-full select-none transition-all duration-200 bg-black h-full overflow-hidden",
174
  {
175
  "pointer-events-none": isResizing || isAiWorking,
176
  "lg:max-w-md lg:mx-auto lg:!rounded-[42px] lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:h-[80dvh] lg:max-h-[996px]":
@@ -179,56 +208,25 @@ export const Preview = ({
179
  currentTab !== "preview" && device === "desktop",
180
  }
181
  )}
182
- >
183
- <SandpackProvider
184
- template="static"
185
- options={{
186
- classes: {
187
- "sp-wrapper": "!w-full !h-full",
188
- "sp-layout": "!w-full !h-full",
189
- "sp-stack": "!w-full !h-full",
190
- },
191
- activeFile: currentPage,
192
- }}
193
- files={formattedPages}
194
- >
195
- <SandpackLayout>
196
- <SandpackPreviewClient ref={iframeRef!} />
197
- </SandpackLayout>
198
- </SandpackProvider>
199
- </div>
 
200
  </div>
201
  );
202
  };
203
-
204
- const SandpackPreviewClient = ({
205
- ref,
206
- }: {
207
- ref: React.RefObject<SandpackPreviewRef | null>;
208
- }) => {
209
- const { sandpack } = useSandpack();
210
-
211
- useUpdateEffect(() => {
212
- const client = ref.current?.getClient();
213
- const clientId = ref.current?.clientId;
214
-
215
- if (client && clientId) {
216
- // console.log({ client });
217
- // console.log(sandpack.clients[clientId]);
218
- // const iframe = client.iframe;
219
- // console.log(iframe.contentWindow);
220
- }
221
- /**
222
- * NOTE: In order to make sure that the client will be available
223
- * use the whole `sandpack` object as a dependency.
224
- */
225
- }, [sandpack]);
226
-
227
- return (
228
- <SandpackPreview
229
- ref={ref}
230
- showRefreshButton={false}
231
- showOpenInCodeSandbox={false}
232
- />
233
- );
234
- };
 
3
  import { useMemo, useState } from "react";
4
  import classNames from "classnames";
5
  import { toast } from "sonner";
6
+ import { useThrottleFn } from "react-use";
 
 
 
 
 
 
7
 
8
  import { cn } from "@/lib/utils";
9
  import { GridPattern } from "@/components/magic-ui/grid-pattern";
 
11
  import { Page } from "@/types";
12
 
13
  export const Preview = ({
14
+ html,
15
  isResizing,
16
  isAiWorking,
17
  ref,
 
19
  currentTab,
20
  iframeRef,
21
  pages,
22
+ setCurrentPage,
 
23
  isEditableModeEnabled,
24
+ onClickElement,
25
+ }: {
26
  html: string;
27
  isResizing: boolean;
28
  isAiWorking: boolean;
29
  pages: Page[];
 
30
  setCurrentPage: React.Dispatch<React.SetStateAction<string>>;
31
  ref: React.RefObject<HTMLDivElement | null>;
32
+ iframeRef?: React.RefObject<HTMLIFrameElement | null>;
33
  device: "desktop" | "mobile";
34
  currentTab: string;
35
  isEditableModeEnabled?: boolean;
36
  onClickElement?: (element: HTMLElement) => void;
37
  }) => {
38
+ const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(
39
+ null
40
+ );
41
+
42
+ const handleMouseOver = (event: MouseEvent) => {
43
+ if (iframeRef?.current) {
44
+ const iframeDocument = iframeRef.current.contentDocument;
45
+ if (iframeDocument) {
46
+ const targetElement = event.target as HTMLElement;
47
+ if (
48
+ hoveredElement !== targetElement &&
49
+ targetElement !== iframeDocument.body
50
+ ) {
51
+ setHoveredElement(targetElement);
52
+ targetElement.classList.add("hovered-element");
53
+ } else {
54
+ return setHoveredElement(null);
55
+ }
56
+ }
57
+ }
58
+ };
59
+ const handleMouseOut = () => {
60
+ setHoveredElement(null);
61
+ };
62
+ const handleClick = (event: MouseEvent) => {
63
+ if (iframeRef?.current) {
64
+ const iframeDocument = iframeRef.current.contentDocument;
65
+ if (iframeDocument) {
66
+ const targetElement = event.target as HTMLElement;
67
+ if (targetElement !== iframeDocument.body) {
68
+ onClickElement?.(targetElement);
69
+ }
70
+ }
71
+ }
72
+ };
73
+ const handleCustomNavigation = (event: MouseEvent) => {
74
+ if (iframeRef?.current) {
75
+ const iframeDocument = iframeRef.current.contentDocument;
76
+ if (iframeDocument) {
77
+ const findClosestAnchor = (
78
+ element: HTMLElement
79
+ ): HTMLAnchorElement | null => {
80
+ let current = element;
81
+ while (current && current !== iframeDocument.body) {
82
+ if (current.tagName === "A") {
83
+ return current as HTMLAnchorElement;
84
+ }
85
+ current = current.parentElement as HTMLElement;
86
+ }
87
+ return null;
88
+ };
89
+
90
+ const anchorElement = findClosestAnchor(event.target as HTMLElement);
91
 
92
+ if (anchorElement) {
93
+ let href = anchorElement.getAttribute("href");
94
+ if (href) {
95
+ event.stopPropagation();
96
+ event.preventDefault();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ if (href.includes("#") && !href.includes(".html")) {
99
+ const targetElement = iframeDocument.querySelector(href);
100
+ if (targetElement) {
101
+ targetElement.scrollIntoView({ behavior: "smooth" });
102
+ }
103
+ return;
104
+ }
 
 
 
 
105
 
106
+ href = href.split(".html")[0] + ".html";
107
+ const isPageExist = pages.some((page) => page.path === href);
108
+ if (isPageExist) {
109
+ setCurrentPage(href);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ };
116
 
117
+ useUpdateEffect(() => {
118
+ const cleanupListeners = () => {
119
+ if (iframeRef?.current?.contentDocument) {
120
+ const iframeDocument = iframeRef.current.contentDocument;
121
+ iframeDocument.removeEventListener("mouseover", handleMouseOver);
122
+ iframeDocument.removeEventListener("mouseout", handleMouseOut);
123
+ iframeDocument.removeEventListener("click", handleClick);
124
+ }
125
+ };
126
 
127
+ if (iframeRef?.current) {
128
+ const iframeDocument = iframeRef.current.contentDocument;
129
+ if (iframeDocument) {
130
+ cleanupListeners();
131
+
132
+ if (isEditableModeEnabled) {
133
+ iframeDocument.addEventListener("mouseover", handleMouseOver);
134
+ iframeDocument.addEventListener("mouseout", handleMouseOut);
135
+ iframeDocument.addEventListener("click", handleClick);
136
+ }
137
+ }
138
+ }
139
+
140
+ return cleanupListeners;
141
+ }, [iframeRef, isEditableModeEnabled]);
142
 
143
  const selectedElement = useMemo(() => {
144
  if (!isEditableModeEnabled) return null;
 
146
  return hoveredElement;
147
  }, [hoveredElement, isEditableModeEnabled]);
148
 
149
+ const throttledHtml = useThrottleFn((html) => html, 1000, [html]);
 
 
 
 
 
150
 
151
  return (
152
  <div
 
194
  </span>
195
  </div>
196
  )}
197
+ <iframe
198
  id="preview-iframe"
199
+ ref={iframeRef}
200
  title="output"
201
  className={classNames(
202
+ "w-full select-none transition-all duration-200 bg-black h-full",
203
  {
204
  "pointer-events-none": isResizing || isAiWorking,
205
  "lg:max-w-md lg:mx-auto lg:!rounded-[42px] lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:h-[80dvh] lg:max-h-[996px]":
 
208
  currentTab !== "preview" && device === "desktop",
209
  }
210
  )}
211
+ srcDoc={throttledHtml as string}
212
+ onLoad={() => {
213
+ if (iframeRef?.current?.contentWindow?.document?.body) {
214
+ iframeRef.current.contentWindow.document.body.scrollIntoView({
215
+ block: isAiWorking ? "end" : "start",
216
+ inline: "nearest",
217
+ behavior: isAiWorking ? "instant" : "smooth",
218
+ });
219
+ }
220
+ // add event listener to all links in the iframe to handle navigation
221
+ if (iframeRef?.current?.contentWindow?.document) {
222
+ const links =
223
+ iframeRef.current.contentWindow.document.querySelectorAll("a");
224
+ links.forEach((link) => {
225
+ link.addEventListener("click", handleCustomNavigation);
226
+ });
227
+ }
228
+ }}
229
+ />
230
  </div>
231
  );
232
  };