From 296f800e19310559d9f9ed7b8718e9805421a474 Mon Sep 17 00:00:00 2001 From: surya4419 Date: Fri, 28 Mar 2025 14:27:17 +0530 Subject: [PATCH 1/4] fix: prevent full body re-render when selecting a sample from Load Sample dropdown Signed-off-by: surya4419 --- src/components/SampleDropdown.tsx | 6 +-- src/store/store.ts | 82 ++++++++++++++++--------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/components/SampleDropdown.tsx b/src/components/SampleDropdown.tsx index 070aabe8..cf9fd26a 100644 --- a/src/components/SampleDropdown.tsx +++ b/src/components/SampleDropdown.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { Button, Dropdown, Space, message, MenuProps } from "antd"; import { DownOutlined } from "@ant-design/icons"; import { useCallback, useMemo, useState } from "react"; @@ -5,7 +6,7 @@ import useAppStore from "../store/store"; import { shallow } from "zustand/shallow"; import { useStoreWithEqualityFn } from "zustand/traditional"; -function SampleDropdown({ +const SampleDropdown = React.memo(function SampleDropdown({ setLoading, }: { setLoading: React.Dispatch>; @@ -60,6 +61,5 @@ function SampleDropdown({ ); -} - +}) export default SampleDropdown; diff --git a/src/store/store.ts b/src/store/store.ts index 52d697f3..bcb9fa1d 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -89,9 +89,7 @@ const useAppStore = create()( const compressedData = params.get("data"); if (compressedData) { await get().loadFromLink(compressedData); - } else { - await get().rebuild(); - } + } }, loadSample: async (name: string) => { const sample = SAMPLES.find((s) => s.NAME === name); @@ -107,76 +105,92 @@ const useAppStore = create()( data: JSON.stringify(sample.DATA, null, 2), editorAgreementData: JSON.stringify(sample.DATA, null, 2), })); - await get().rebuild(); + get().rebuild(); + } }, rebuild: async () => { const { templateMarkdown, modelCto, data } = get(); try { const result = await rebuildDeBounce(templateMarkdown, modelCto, data); - set(() => ({ agreementHtml: result, error: undefined })); // Clear error on success + set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { - set(() => ({ error: formatError(error) })); + set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); } }, setTemplateMarkdown: async (template: string) => { - set(() => ({ templateMarkdown: template })); + set(() => ({ + templateMarkdown: template, + editorValue: template +})); const { modelCto, data } = get(); try { const result = await rebuildDeBounce(template, modelCto, data); - set(() => ({ agreementHtml: result, error: undefined })); // Clear error on success + set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { - set(() => ({ error: formatError(error) })); + set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); } }, setEditorValue: (value: string) => { set(() => ({ editorValue: value })); }, setModelCto: async (model: string) => { - set(() => ({ modelCto: model })); + set(() => ({ + modelCto: model, + editorModelCto: model +})); const { templateMarkdown, data } = get(); try { const result = await rebuildDeBounce(templateMarkdown, model, data); - set(() => ({ agreementHtml: result, error: undefined })); // Clear error on success + set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { - set(() => ({ error: formatError(error) })); + set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); } }, setEditorModelCto: (value: string) => { set(() => ({ editorModelCto: value })); }, setData: async (data: string) => { - set(() => ({ data })); + set(() => ({ + data, + editorAgreementData: data +})); try { const result = await rebuildDeBounce( get().templateMarkdown, get().modelCto, data ); - set(() => ({ agreementHtml: result, error: undefined })); // Clear error on success + set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { - set(() => ({ error: formatError(error) })); + set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); } }, setEditorAgreementData: (value: string) => { set(() => ({ editorAgreementData: value })); }, generateShareableLink: () => { - const state = get(); - const compressedData = compress({ - templateMarkdown: state.templateMarkdown, - modelCto: state.modelCto, - data: state.data, - agreementHtml: state.agreementHtml, - }); - return `${window.location.origin}?data=${compressedData}`; + try { + const state = get(); + const compressedData = compress({ + templateMarkdown: state.templateMarkdown, + modelCto: state.modelCto, + data: state.data, + agreementHtml: state.agreementHtml, + }); + return `${window.location.origin}?data=${compressedData}`; + } catch (error) { + set(() => ({ error: 'Failed to generate share link: ' + (error instanceof Error ? error.message : 'Unknown error') })); + return window.location.href; + } }, loadFromLink: async (compressedData: string) => { try { - const { templateMarkdown, modelCto, data, agreementHtml } = decompress(compressedData); - if (!templateMarkdown || !modelCto || !data) { - throw new Error("Invalid share link data"); + const decompressed = decompress(compressedData); + if (!decompressed?.templateMarkdown || !decompressed?.modelCto || !decompressed?.data) { + throw new Error("Invalid share link - missing required fields"); } + const { templateMarkdown, modelCto, data, agreementHtml } = decompressed; set(() => ({ templateMarkdown, editorValue: templateMarkdown, @@ -185,9 +199,9 @@ const useAppStore = create()( data, editorAgreementData: data, agreementHtml, - error: undefined, + error: undefined })); - await get().rebuild(); + } catch (error) { set(() => ({ error: "Failed to load shared content: " + (error instanceof Error ? error.message : "Unknown error"), @@ -209,15 +223,3 @@ const useAppStore = create()( export default useAppStore; - -function formatError(error: any): string { - console.error(error); - if (typeof error === "string") return error; - if (Array.isArray(error)) return error.map((e) => formatError(e)).join("\n"); - if (error.code) { - const sub = error.errors ? formatError(error.errors) : ""; - const msg = error.renderedMessage || ""; - return `Error: ${error.code} ${sub} ${msg}`; - } - return error.toString(); -} \ No newline at end of file From bd2f8dad196bcd50ff34e5d56191cbb944f6f7aa Mon Sep 17 00:00:00 2001 From: surya4419 Date: Fri, 28 Mar 2025 15:44:44 +0530 Subject: [PATCH 2/4] fix: prevent full body re-render when selecting a sample from Load Sample dropdown Signed-off-by: surya4419 --- src/components/SampleDropdown.tsx | 15 ++++++++------- src/store/store.ts | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/components/SampleDropdown.tsx b/src/components/SampleDropdown.tsx index cf9fd26a..40db7afc 100644 --- a/src/components/SampleDropdown.tsx +++ b/src/components/SampleDropdown.tsx @@ -1,26 +1,27 @@ import React from "react"; import { Button, Dropdown, Space, message, MenuProps } from "antd"; import { DownOutlined } from "@ant-design/icons"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useMemo } from "react"; import useAppStore from "../store/store"; import { shallow } from "zustand/shallow"; import { useStoreWithEqualityFn } from "zustand/traditional"; -const SampleDropdown = React.memo(function SampleDropdown({ +const SampleDropdown = function SampleDropdown({ setLoading, }: { setLoading: React.Dispatch>; }): JSX.Element { - const { samples, loadSample } = useStoreWithEqualityFn( + const { samples, loadSample, sampleName } = useStoreWithEqualityFn( useAppStore, (state) => ({ samples: state.samples, loadSample: state.loadSample as (key: string) => Promise, + sampleName: state.sampleName }), shallow ); - const [selectedSample, setSelectedSample] = useState(null); + const items: MenuProps["items"] = useMemo( () => @@ -38,7 +39,7 @@ const SampleDropdown = React.memo(function SampleDropdown({ try { await loadSample(e.key); void message.info(`Loaded ${e.key} sample`); - setSelectedSample(e.key); + } catch (error) { void message.error("Failed to load sample"); } finally { @@ -55,11 +56,11 @@ const SampleDropdown = React.memo(function SampleDropdown({ void handleMenuClick(e) }} trigger={["click"]}>
); -}) +} export default SampleDropdown; diff --git a/src/store/store.ts b/src/store/store.ts index bcb9fa1d..72cc5188 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -89,7 +89,9 @@ const useAppStore = create()( const compressedData = params.get("data"); if (compressedData) { await get().loadFromLink(compressedData); - } + } else { + await get().loadSample(playground.NAME); + } }, loadSample: async (name: string) => { const sample = SAMPLES.find((s) => s.NAME === name); From aee51a0206e5d9f81bc749c22245538511db7adb Mon Sep 17 00:00:00 2001 From: surya4419 Date: Wed, 2 Apr 2025 14:16:48 +0530 Subject: [PATCH 3/4] fix: prevent full body re-render when selecting a sample from Load Sample dropdown Signed-off-by: surya4419 --- src/AgreementHtml.tsx | 9 +++++---- src/store/store.ts | 20 +++++++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/AgreementHtml.tsx b/src/AgreementHtml.tsx index 031cb9f6..f70b6b73 100644 --- a/src/AgreementHtml.tsx +++ b/src/AgreementHtml.tsx @@ -4,10 +4,11 @@ import { Spin } from "antd"; import useAppStore from "./store/store"; import FullScreenModal from "./components/FullScreenModal"; -function AgreementHtml({ loading, isModal }: { loading: boolean; isModal?: boolean }) { +function AgreementHtml({ isModal }: { isModal?: boolean; loading?: boolean }) { const agreementHtml = useAppStore((state) => state.agreementHtml); const backgroundColor = useAppStore((state) => state.backgroundColor); const textColor = useAppStore((state) => state.textColor); + const isLoading = useAppStore((state) => state.isLoading); return (
The result of merging the JSON data with the template.

- {loading ? ( -
- } /> + {isLoading ? ( +
+ } />
) : (
Promise; setEditorValue: (value: string) => void; setModelCto: (model: string) => Promise; @@ -44,8 +45,6 @@ export interface DecompressedData { agreementHtml: string; } -const rebuildDeBounce = debounce(rebuild, 500); - async function rebuild(template: string, model: string, dataString: string) { const modelManager = new ModelManager({ strict: true }); modelManager.addCTOModel(model, undefined, true); @@ -69,6 +68,11 @@ async function rebuild(template: string, model: string, dataString: string) { ); } +const rebuildDeBounce = debounce(async (template: string, model: string, data: string) => { + const result = await rebuild(template, model, data); + return result; +}, 500); + const useAppStore = create()( immer( devtools((set, get) => ({ @@ -83,6 +87,7 @@ const useAppStore = create()( editorAgreementData: JSON.stringify(playground.DATA, null, 2), agreementHtml: "", error: undefined, + isLoading: false, samples: SAMPLES, init: async () => { const params = new URLSearchParams(window.location.search); @@ -108,16 +113,21 @@ const useAppStore = create()( editorAgreementData: JSON.stringify(sample.DATA, null, 2), })); get().rebuild(); - } }, rebuild: async () => { - const { templateMarkdown, modelCto, data } = get(); + set(() => ({ isLoading: true })); try { - const result = await rebuildDeBounce(templateMarkdown, modelCto, data); + const result = await rebuildDeBounce( + get().templateMarkdown, + get().modelCto, + get().data + ); set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); + } finally { + set(() => ({ isLoading: false })); } }, setTemplateMarkdown: async (template: string) => { From 732c6eb2489f8df42591dd58687d874bee20ff72 Mon Sep 17 00:00:00 2001 From: surya4419 Date: Wed, 2 Apr 2025 16:05:24 +0530 Subject: [PATCH 4/4] fix: prevent full body re-render when selecting a sample from Load Sample dropdown Signed-off-by: surya4419 --- src/store/store.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/store/store.ts b/src/store/store.ts index b928045f..083cef90 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -125,7 +125,7 @@ const useAppStore = create()( ); set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { - set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); + set(() => ({ error: formatError(error) })); } finally { set(() => ({ isLoading: false })); } @@ -140,7 +140,7 @@ const useAppStore = create()( const result = await rebuildDeBounce(template, modelCto, data); set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { - set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); + set(() => ({ error: formatError(error) })); } }, setEditorValue: (value: string) => { @@ -156,7 +156,7 @@ const useAppStore = create()( const result = await rebuildDeBounce(templateMarkdown, model, data); set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { - set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); + set(() => ({ error: formatError(error) })); } }, setEditorModelCto: (value: string) => { @@ -175,7 +175,7 @@ const useAppStore = create()( ); set(() => ({ agreementHtml: result, error: undefined })); } catch (error: any) { - set(() => ({ error: error instanceof Error ? error.message : 'Unknown error' })); + set(() => ({ error: formatError(error) })); } }, setEditorAgreementData: (value: string) => { @@ -235,3 +235,15 @@ const useAppStore = create()( export default useAppStore; + +function formatError(error: any): string { + console.error(error); + if (typeof error === "string") return error; + if (Array.isArray(error)) return error.map((e) => formatError(e)).join("\n"); + if (error.code) { + const sub = error.errors ? formatError(error.errors) : ""; + const msg = error.renderedMessage || ""; + return `Error: ${error.code} ${sub} ${msg}`; + } + return error.toString(); +} \ No newline at end of file