From 160b2d1630f2fbb325fa8eef8bb59c92e3ed6d8f Mon Sep 17 00:00:00 2001 From: surya4419 Date: Fri, 28 Mar 2025 14:27:17 +0530 Subject: [PATCH 1/6] 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 e832901044c89fae58c74f3ed0b6188532d2ca27 Mon Sep 17 00:00:00 2001 From: surya4419 Date: Fri, 28 Mar 2025 15:44:44 +0530 Subject: [PATCH 2/6] 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 8b5565dc0eb69237034b45263e4b53d923b1e9ce Mon Sep 17 00:00:00 2001 From: surya4419 Date: Wed, 2 Apr 2025 14:16:48 +0530 Subject: [PATCH 3/6] 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 cdd6ae2a6e749e53678b3f43c23f4c0693fa7de0 Mon Sep 17 00:00:00 2001 From: surya4419 Date: Wed, 2 Apr 2025 16:05:24 +0530 Subject: [PATCH 4/6] 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 From 97f601bae51643c3c6e8d5880651edabccfb7f2f Mon Sep 17 00:00:00 2001 From: surya4419 Date: Thu, 24 Apr 2025 22:20:08 +0530 Subject: [PATCH 5/6] fix(ui): apply dark/light theme styles to Load Sample and Share buttons Signed-off-by: surya4419 --- src/components/SampleDropdown.tsx | 37 +++++++++++++++++++++++++++---- src/components/UseShare.tsx | 19 +++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/components/SampleDropdown.tsx b/src/components/SampleDropdown.tsx index 40db7afc..37f3a87b 100644 --- a/src/components/SampleDropdown.tsx +++ b/src/components/SampleDropdown.tsx @@ -11,12 +11,13 @@ const SampleDropdown = function SampleDropdown({ }: { setLoading: React.Dispatch>; }): JSX.Element { - const { samples, loadSample, sampleName } = useStoreWithEqualityFn( + const { samples, loadSample, sampleName, backgroundColor } = useStoreWithEqualityFn( useAppStore, (state) => ({ samples: state.samples, loadSample: state.loadSample as (key: string) => Promise, - sampleName: state.sampleName + sampleName: state.sampleName, + backgroundColor: state.backgroundColor }), shallow ); @@ -28,6 +29,9 @@ const SampleDropdown = function SampleDropdown({ samples?.map((s) => ({ label: s.NAME, key: s.NAME, + style: backgroundColor === '#121212' ? { + color: '#fff' + } : undefined })) || [], [samples] ); @@ -53,9 +57,34 @@ const SampleDropdown = function SampleDropdown({ return ( - void handleMenuClick(e) }} trigger={["click"]}> + void handleMenuClick(e), + style: backgroundColor === '#121212' ? { + backgroundColor: '#1f1f1f', + color: '#fff', + } : undefined, + }} + trigger={["click"]} + dropdownRender={menu => ( +
+ {menu} +
+ )} + >
-
diff --git a/src/components/UseShare.tsx b/src/components/UseShare.tsx index b0b9fb6f..11efb405 100644 --- a/src/components/UseShare.tsx +++ b/src/components/UseShare.tsx @@ -2,10 +2,15 @@ import { useState } from "react"; import { Button, message } from "antd"; import { ShareAltOutlined } from "@ant-design/icons"; import useAppStore from "../store/store"; +import { shallow } from "zustand/shallow"; const UseShare = () => { - const generateShareableLink = useAppStore( - (state) => state.generateShareableLink + const { generateShareableLink, backgroundColor } = useAppStore( + (state) => ({ + generateShareableLink: state.generateShareableLink, + backgroundColor: state.backgroundColor + }), + shallow ); const [copied, setCopied] = useState(false); @@ -24,7 +29,15 @@ const UseShare = () => { return (
-
From 86de3fd5c4d98d935ea9e816c058ea300edab235 Mon Sep 17 00:00:00 2001 From: surya4419 Date: Thu, 24 Apr 2025 23:15:06 +0530 Subject: [PATCH 6/6] fix(ui): apply dark/light theme styles to Load Sample and Share buttons Signed-off-by: surya4419 --- src/components/SampleDropdown.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/SampleDropdown.tsx b/src/components/SampleDropdown.tsx index 37f3a87b..481e1854 100644 --- a/src/components/SampleDropdown.tsx +++ b/src/components/SampleDropdown.tsx @@ -27,13 +27,10 @@ const SampleDropdown = function SampleDropdown({ const items: MenuProps["items"] = useMemo( () => samples?.map((s) => ({ - label: s.NAME, + label: {s.NAME}, key: s.NAME, - style: backgroundColor === '#121212' ? { - color: '#fff' - } : undefined })) || [], - [samples] + [samples, backgroundColor] ); const handleMenuClick = useCallback( @@ -63,14 +60,14 @@ const SampleDropdown = function SampleDropdown({ onClick: (e) => void handleMenuClick(e), style: backgroundColor === '#121212' ? { backgroundColor: '#1f1f1f', - color: '#fff', + color: '#ffffff', } : undefined, }} trigger={["click"]} dropdownRender={menu => (
{menu}