diff --git a/package-lock.json b/package-lock.json
index 1d30ce3..f892844 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
"@create-figma-plugin/ui": "^3.2",
"base64-js": "^1.5",
"classnames": "^2.5",
- "preact": "^10.21",
+ "preact": "^10.22",
"react-hook-form": "^7.51",
"romans": "^2.0",
"slugify": "^1.6",
@@ -6722,9 +6722,9 @@
"dev": true
},
"node_modules/preact": {
- "version": "10.21.0",
- "resolved": "https://registry.npmjs.org/preact/-/preact-10.21.0.tgz",
- "integrity": "sha512-aQAIxtzWEwH8ou+OovWVSVNlFImL7xUCwJX3YMqA3U8iKCNC34999fFOnWjYNsylgfPgMexpbk7WYOLtKr/mxg==",
+ "version": "10.22.0",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz",
+ "integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
diff --git a/package.json b/package.json
index c455105..2d9c357 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"@create-figma-plugin/ui": "^3.2",
"base64-js": "^1.5",
"classnames": "^2.5",
- "preact": "^10.21",
+ "preact": "^10.22",
"react-hook-form": "^7.51",
"romans": "^2.0",
"slugify": "^1.6",
diff --git a/ui-src/App.tsx b/ui-src/App.tsx
index f4bc015..306ad64 100644
--- a/ui-src/App.tsx
+++ b/ui-src/App.tsx
@@ -5,6 +5,7 @@ import Penpot from '@ui/assets/penpot.svg?react';
import { PenpotExporter } from '@ui/components/PenpotExporter';
import { Stack } from '@ui/components/Stack';
import { Wrapper } from '@ui/components/Wrapper';
+import { FigmaProvider } from '@ui/context/FigmaContext';
// Safe default value to avoid overflowing from the screen
const MAX_HEIGHT = 800;
@@ -21,18 +22,20 @@ export const App = () => {
}, [height]);
return (
- MAX_HEIGHT}>
-
-
-
-
-
+
+ MAX_HEIGHT}>
+
+
+
+
+
+
);
};
diff --git a/ui-src/components/ExportForm.tsx b/ui-src/components/ExportForm.tsx
new file mode 100644
index 0000000..5c963d3
--- /dev/null
+++ b/ui-src/components/ExportForm.tsx
@@ -0,0 +1,32 @@
+import { Button } from '@create-figma-plugin/ui';
+import { FormProvider, useForm } from 'react-hook-form';
+
+import { Stack } from '@ui/components/Stack';
+import { useFigma } from '@ui/context';
+
+import { MissingFontsSection } from './MissingFontsSection';
+
+export type FormValues = Record;
+
+export const ExportForm = () => {
+ const { cancel, exportPenpot } = useFigma();
+ const methods = useForm();
+
+ return (
+
+
+
+ );
+};
diff --git a/ui-src/components/ExporterProgress.tsx b/ui-src/components/ExporterProgress.tsx
index 549b99d..ae3fded 100644
--- a/ui-src/components/ExporterProgress.tsx
+++ b/ui-src/components/ExporterProgress.tsx
@@ -1,35 +1,11 @@
import { LoadingIndicator } from '@create-figma-plugin/ui';
-import { useEffect, useState } from 'react';
+
+import { useFigma } from '@ui/context';
import { Stack } from './Stack';
-type ExporterProgressProps = {
- downloading: boolean;
-};
-
-export const ExporterProgress = ({ downloading }: ExporterProgressProps) => {
- const [currentNode, setCurrentNode] = useState();
- const [totalPages, setTotalPages] = useState();
- const [processedPages, setProcessedPages] = useState();
-
- const onMessage = (event: MessageEvent<{ pluginMessage: { type: string; data: unknown } }>) => {
- if (event.data.pluginMessage?.type === 'PROGRESS_NODE') {
- setCurrentNode(event.data.pluginMessage.data as string);
- } else if (event.data.pluginMessage?.type === 'PROGRESS_TOTAL_PAGES') {
- setTotalPages(event.data.pluginMessage.data as number);
- setProcessedPages(0);
- } else if (event.data.pluginMessage?.type === 'PROGRESS_PROCESSED_PAGES') {
- setProcessedPages(event.data.pluginMessage.data as number);
- }
- };
-
- useEffect(() => {
- window.addEventListener('message', onMessage);
-
- return () => {
- window.removeEventListener('message', onMessage);
- };
- }, []);
+export const ExporterProgress = () => {
+ const { currentNode, totalPages, processedPages, downloading } = useFigma();
const truncateText = (text: string, maxChars: number) => {
if (text.length <= maxChars) {
diff --git a/ui-src/components/MissingFontsSection.tsx b/ui-src/components/MissingFontsSection.tsx
index f4a4c0e..5c9863c 100644
--- a/ui-src/components/MissingFontsSection.tsx
+++ b/ui-src/components/MissingFontsSection.tsx
@@ -1,20 +1,20 @@
import { Banner, IconInfo32, Link, Textbox } from '@create-figma-plugin/ui';
import { Controller, useFormContext } from 'react-hook-form';
+import { useFigma } from '@ui/context';
+
import { Stack } from './Stack';
-type MissingFontsSectionProps = {
- fonts?: string[];
-};
+export const MissingFontsSection = () => {
+ const { missingFonts } = useFigma();
-export const MissingFontsSection = ({ fonts }: MissingFontsSectionProps) => {
- if (!fonts || !fonts.length) return null;
+ if (!missingFonts || !missingFonts.length) return null;
return (
}>
- {fonts.length} custom font{fonts.length > 1 ? 's' : ''} detected
+ {missingFonts.length} custom font{missingFonts.length > 1 ? 's' : ''} detected
To export your file with custom fonts, please follow these steps:
@@ -43,7 +43,7 @@ export const MissingFontsSection = ({ fonts }: MissingFontsSectionProps) => {
Return here and paste the font IDs in the section below
- {fonts.map(font => (
+ {missingFonts.map(font => (
{font}
diff --git a/ui-src/components/PenpotExporter.tsx b/ui-src/components/PenpotExporter.tsx
index 4061d34..49b52eb 100644
--- a/ui-src/components/PenpotExporter.tsx
+++ b/ui-src/components/PenpotExporter.tsx
@@ -1,114 +1,19 @@
-import { Banner, Button, IconInfo32, LoadingIndicator } from '@create-figma-plugin/ui';
-import { useEffect, useState } from 'react';
-import { FormProvider, useForm } from 'react-hook-form';
+import { LoadingIndicator } from '@create-figma-plugin/ui';
-import { Stack } from '@ui/components/Stack';
-import { parse } from '@ui/parser';
-import { PenpotDocument } from '@ui/types';
+import { useFigma } from '@ui/context';
+import { ExportForm } from './ExportForm';
import { ExporterProgress } from './ExporterProgress';
-import { MissingFontsSection } from './MissingFontsSection';
-
-type FormValues = Record;
+import { PluginReload } from './PluginReload';
export const PenpotExporter = () => {
- const [missingFonts, setMissingFonts] = useState();
- const [needsReload, setNeedsReload] = useState(false);
- const [loading, setLoading] = useState(true);
- const [exporting, setExporting] = useState(false);
- const [downloading, setDownloading] = useState(false);
- const methods = useForm();
-
- methods.getValues();
-
- const onMessage = (event: MessageEvent<{ pluginMessage: { type: string; data: unknown } }>) => {
- if (event.data.pluginMessage?.type == 'PENPOT_DOCUMENT') {
- setDownloading(true);
-
- const document = event.data.pluginMessage.data as PenpotDocument;
- const file = parse(document);
-
- file.export();
- } else if (event.data.pluginMessage?.type == 'CUSTOM_FONTS') {
- setMissingFonts(event.data.pluginMessage.data as string[]);
- setLoading(false);
- setNeedsReload(false);
- } else if (event.data.pluginMessage?.type == 'CHANGES_DETECTED') {
- setNeedsReload(true);
- }
- };
-
- const exportPenpot = (data: FormValues) => {
- setExporting(true);
-
- parent.postMessage(
- {
- pluginMessage: {
- type: 'export',
- data
- }
- },
- '*'
- );
- };
-
- const cancel = () => {
- parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*');
- };
-
- const reload = () => {
- setLoading(true);
- parent.postMessage({ pluginMessage: { type: 'reload' } }, '*');
- };
-
- useEffect(() => {
- window.addEventListener('message', onMessage);
-
- parent.postMessage({ pluginMessage: { type: 'ready' } }, '*');
-
- return () => {
- window.removeEventListener('message', onMessage);
- };
- }, []);
+ const { loading, needsReload, exporting } = useFigma();
if (loading) return ;
- if (exporting) return ;
+ if (exporting) return ;
- if (needsReload) {
- return (
-
- }>
- Changes detected. Please reload the plug-in to ensure all modifications are included in
- the exported file.
-
-
-
-
-
-
- );
- }
+ if (needsReload) return ;
- return (
-
-
-
- );
+ return ;
};
diff --git a/ui-src/components/PluginReload.tsx b/ui-src/components/PluginReload.tsx
new file mode 100644
index 0000000..d20ee84
--- /dev/null
+++ b/ui-src/components/PluginReload.tsx
@@ -0,0 +1,25 @@
+import { Banner, Button, IconInfo32 } from '@create-figma-plugin/ui';
+
+import { Stack } from '@ui/components/Stack';
+import { useFigma } from '@ui/context';
+
+export const PluginReload = () => {
+ const { reload, cancel } = useFigma();
+
+ return (
+
+ }>
+ Changes detected. Please reload the plug-in to ensure all modifications are included in the
+ exported file.
+
+
+
+
+
+
+ );
+};
diff --git a/ui-src/context/FigmaContext.tsx b/ui-src/context/FigmaContext.tsx
new file mode 100644
index 0000000..61c3350
--- /dev/null
+++ b/ui-src/context/FigmaContext.tsx
@@ -0,0 +1,113 @@
+import { JSX, PropsWithChildren, useEffect, useState } from 'react';
+
+import { FormValues } from '@ui/components/ExportForm';
+import { parse } from '@ui/parser';
+import { PenpotDocument } from '@ui/types';
+
+import { createGenericContext } from './createGenericContext';
+
+type Context = {
+ missingFonts: string[] | undefined;
+ needsReload: boolean;
+ loading: boolean;
+ exporting: boolean;
+ downloading: boolean;
+ currentNode: string | undefined;
+ totalPages: number | undefined;
+ processedPages: number | undefined;
+ reload: () => void;
+ cancel: () => void;
+ exportPenpot: (data: FormValues) => void;
+};
+
+const [useFigma, StateContextProvider] = createGenericContext();
+
+const FigmaProvider = ({ children }: PropsWithChildren): JSX.Element => {
+ const [missingFonts, setMissingFonts] = useState();
+ const [needsReload, setNeedsReload] = useState(false);
+ const [loading, setLoading] = useState(true);
+ const [exporting, setExporting] = useState(false);
+ const [downloading, setDownloading] = useState(false);
+ const [currentNode, setCurrentNode] = useState();
+ const [totalPages, setTotalPages] = useState();
+ const [processedPages, setProcessedPages] = useState();
+
+ const onMessage = (event: MessageEvent<{ pluginMessage: { type: string; data: unknown } }>) => {
+ if (event.data.pluginMessage?.type == 'PENPOT_DOCUMENT') {
+ setDownloading(true);
+
+ const document = event.data.pluginMessage.data as PenpotDocument;
+ const file = parse(document);
+
+ file.export();
+ } else if (event.data.pluginMessage?.type == 'CUSTOM_FONTS') {
+ setMissingFonts(event.data.pluginMessage.data as string[]);
+ setLoading(false);
+ setNeedsReload(false);
+ } else if (event.data.pluginMessage?.type == 'CHANGES_DETECTED') {
+ setNeedsReload(true);
+ } else if (event.data.pluginMessage?.type === 'PROGRESS_NODE') {
+ setCurrentNode(event.data.pluginMessage.data as string);
+ } else if (event.data.pluginMessage?.type === 'PROGRESS_TOTAL_PAGES') {
+ setTotalPages(event.data.pluginMessage.data as number);
+ setProcessedPages(0);
+ } else if (event.data.pluginMessage?.type === 'PROGRESS_PROCESSED_PAGES') {
+ setProcessedPages(event.data.pluginMessage.data as number);
+ }
+ };
+
+ const reload = () => {
+ setLoading(true);
+ parent.postMessage({ pluginMessage: { type: 'reload' } }, '*');
+ };
+
+ const cancel = () => {
+ parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*');
+ };
+
+ const exportPenpot = (data: FormValues) => {
+ setExporting(true);
+
+ parent.postMessage(
+ {
+ pluginMessage: {
+ type: 'export',
+ data
+ }
+ },
+ '*'
+ );
+ };
+
+ useEffect(() => {
+ window.addEventListener('message', onMessage);
+
+ parent.postMessage({ pluginMessage: { type: 'ready' } }, '*');
+
+ return () => {
+ window.removeEventListener('message', onMessage);
+ };
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export { FigmaProvider, useFigma };
diff --git a/ui-src/context/createGenericContext.ts b/ui-src/context/createGenericContext.ts
new file mode 100644
index 0000000..366b041
--- /dev/null
+++ b/ui-src/context/createGenericContext.ts
@@ -0,0 +1,19 @@
+import { Provider } from 'preact';
+// @TODO: Try to use react
+import { createContext, useContext } from 'react';
+
+export const createGenericContext = (): [() => K, Provider] => {
+ const genericContext = createContext(undefined);
+
+ const useGenericContext = (): K => {
+ const context = useContext(genericContext);
+
+ if (!context) {
+ throw new Error('useGenericContext must be used within a Provider');
+ }
+
+ return context as K;
+ };
+
+ return [useGenericContext, genericContext.Provider];
+};
diff --git a/ui-src/context/index.ts b/ui-src/context/index.ts
new file mode 100644
index 0000000..5c18f31
--- /dev/null
+++ b/ui-src/context/index.ts
@@ -0,0 +1,2 @@
+export * from './createGenericContext';
+export * from './FigmaContext';