mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2024-12-22 13:43:03 -05:00
parent
df30dfd376
commit
4b711b3526
7 changed files with 166 additions and 115 deletions
|
@ -2,14 +2,14 @@ import { Button } from '@create-figma-plugin/ui';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { Stack } from '@ui/components/Stack';
|
import { Stack } from '@ui/components/Stack';
|
||||||
import { useFigma } from '@ui/context';
|
import { useFigmaContext } from '@ui/context';
|
||||||
|
|
||||||
import { MissingFontsSection } from './MissingFontsSection';
|
import { MissingFontsSection } from './MissingFontsSection';
|
||||||
|
|
||||||
export type FormValues = Record<string, string>;
|
export type FormValues = Record<string, string>;
|
||||||
|
|
||||||
export const ExportForm = () => {
|
export const ExportForm = () => {
|
||||||
const { cancel, exportPenpot } = useFigma();
|
const { cancel, exportPenpot } = useFigmaContext();
|
||||||
const methods = useForm<FormValues>();
|
const methods = useForm<FormValues>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { LoadingIndicator } from '@create-figma-plugin/ui';
|
import { LoadingIndicator } from '@create-figma-plugin/ui';
|
||||||
|
|
||||||
import { useFigma } from '@ui/context';
|
import { useFigmaContext } from '@ui/context';
|
||||||
|
|
||||||
import { Stack } from './Stack';
|
import { Stack } from './Stack';
|
||||||
|
|
||||||
export const ExporterProgress = () => {
|
export const ExporterProgress = () => {
|
||||||
const { currentNode, totalPages, processedPages, downloading } = useFigma();
|
const { currentNode, totalPages, processedPages, downloading } = useFigmaContext();
|
||||||
|
|
||||||
const truncateText = (text: string, maxChars: number) => {
|
const truncateText = (text: string, maxChars: number) => {
|
||||||
if (text.length <= maxChars) {
|
if (text.length <= maxChars) {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { Banner, IconInfo32, Link, Textbox } from '@create-figma-plugin/ui';
|
import { Banner, IconInfo32, Link, Textbox } from '@create-figma-plugin/ui';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { useFigma } from '@ui/context';
|
import { useFigmaContext } from '@ui/context';
|
||||||
|
|
||||||
import { Stack } from './Stack';
|
import { Stack } from './Stack';
|
||||||
|
|
||||||
export const MissingFontsSection = () => {
|
export const MissingFontsSection = () => {
|
||||||
const { missingFonts } = useFigma();
|
const { missingFonts } = useFigmaContext();
|
||||||
|
|
||||||
if (!missingFonts || !missingFonts.length) return null;
|
if (!missingFonts || !missingFonts.length) return null;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { LoadingIndicator } from '@create-figma-plugin/ui';
|
import { LoadingIndicator } from '@create-figma-plugin/ui';
|
||||||
|
|
||||||
import { useFigma } from '@ui/context';
|
import { useFigmaContext } from '@ui/context';
|
||||||
|
|
||||||
import { ExportForm } from './ExportForm';
|
import { ExportForm } from './ExportForm';
|
||||||
import { ExporterProgress } from './ExporterProgress';
|
import { ExporterProgress } from './ExporterProgress';
|
||||||
import { PluginReload } from './PluginReload';
|
import { PluginReload } from './PluginReload';
|
||||||
|
|
||||||
export const PenpotExporter = () => {
|
export const PenpotExporter = () => {
|
||||||
const { loading, needsReload, exporting } = useFigma();
|
const { loading, needsReload, exporting } = useFigmaContext();
|
||||||
|
|
||||||
if (loading) return <LoadingIndicator />;
|
if (loading) return <LoadingIndicator />;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Banner, Button, IconInfo32 } from '@create-figma-plugin/ui';
|
import { Banner, Button, IconInfo32 } from '@create-figma-plugin/ui';
|
||||||
|
|
||||||
import { Stack } from '@ui/components/Stack';
|
import { Stack } from '@ui/components/Stack';
|
||||||
import { useFigma } from '@ui/context';
|
import { useFigmaContext } from '@ui/context';
|
||||||
|
|
||||||
export const PluginReload = () => {
|
export const PluginReload = () => {
|
||||||
const { reload, cancel } = useFigma();
|
const { reload, cancel } = useFigmaContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack space="small">
|
<Stack space="small">
|
||||||
|
|
|
@ -1,113 +1,14 @@
|
||||||
import { JSX, PropsWithChildren, useEffect, useState } from 'react';
|
import { JSX, PropsWithChildren } from 'react';
|
||||||
|
|
||||||
import { FormValues } from '@ui/components/ExportForm';
|
|
||||||
import { parse } from '@ui/parser';
|
|
||||||
import { PenpotDocument } from '@ui/types';
|
|
||||||
|
|
||||||
import { createGenericContext } from './createGenericContext';
|
import { createGenericContext } from './createGenericContext';
|
||||||
|
import { UseFigmaHook, useFigma } from './useFigma';
|
||||||
|
|
||||||
type Context = {
|
const [useFigmaContext, StateContextProvider] = createGenericContext<UseFigmaHook>();
|
||||||
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<Context>();
|
|
||||||
|
|
||||||
const FigmaProvider = ({ children }: PropsWithChildren): JSX.Element => {
|
const FigmaProvider = ({ children }: PropsWithChildren): JSX.Element => {
|
||||||
const [missingFonts, setMissingFonts] = useState<string[]>();
|
const hook = useFigma();
|
||||||
const [needsReload, setNeedsReload] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [exporting, setExporting] = useState(false);
|
|
||||||
const [downloading, setDownloading] = useState(false);
|
|
||||||
const [currentNode, setCurrentNode] = useState<string | undefined>();
|
|
||||||
const [totalPages, setTotalPages] = useState<number | undefined>();
|
|
||||||
const [processedPages, setProcessedPages] = useState<number | undefined>();
|
|
||||||
|
|
||||||
const onMessage = (event: MessageEvent<{ pluginMessage: { type: string; data: unknown } }>) => {
|
return <StateContextProvider value={hook}>{children}</StateContextProvider>;
|
||||||
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 (
|
|
||||||
<StateContextProvider
|
|
||||||
value={{
|
|
||||||
missingFonts,
|
|
||||||
needsReload,
|
|
||||||
loading,
|
|
||||||
exporting,
|
|
||||||
downloading,
|
|
||||||
currentNode,
|
|
||||||
totalPages,
|
|
||||||
processedPages,
|
|
||||||
reload,
|
|
||||||
cancel,
|
|
||||||
exportPenpot
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</StateContextProvider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { FigmaProvider, useFigma };
|
export { FigmaProvider, useFigmaContext };
|
||||||
|
|
150
ui-src/context/useFigma.ts
Normal file
150
ui-src/context/useFigma.ts
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { FormValues } from '@ui/components/ExportForm';
|
||||||
|
import { parse } from '@ui/parser';
|
||||||
|
import { PenpotDocument } from '@ui/types';
|
||||||
|
|
||||||
|
export type UseFigmaHook = {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PluginMessage =
|
||||||
|
| PenpotDocumentMessage
|
||||||
|
| CustomFontsMessage
|
||||||
|
| ChangesDetectedMessage
|
||||||
|
| ProgressNodeMessage
|
||||||
|
| ProgressTotalPagesMessage
|
||||||
|
| ProgressProcessedPagesMessage;
|
||||||
|
|
||||||
|
type PenpotDocumentMessage = {
|
||||||
|
type: 'PENPOT_DOCUMENT';
|
||||||
|
data: PenpotDocument;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CustomFontsMessage = {
|
||||||
|
type: 'CUSTOM_FONTS';
|
||||||
|
data: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ChangesDetectedMessage = {
|
||||||
|
type: 'CHANGES_DETECTED';
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProgressNodeMessage = {
|
||||||
|
type: 'PROGRESS_NODE';
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProgressTotalPagesMessage = {
|
||||||
|
type: 'PROGRESS_TOTAL_PAGES';
|
||||||
|
data: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProgressProcessedPagesMessage = {
|
||||||
|
type: 'PROGRESS_PROCESSED_PAGES';
|
||||||
|
data: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFigma = (): UseFigmaHook => {
|
||||||
|
const [missingFonts, setMissingFonts] = useState<string[]>();
|
||||||
|
const [needsReload, setNeedsReload] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [exporting, setExporting] = useState(false);
|
||||||
|
const [downloading, setDownloading] = useState(false);
|
||||||
|
const [currentNode, setCurrentNode] = useState<string | undefined>();
|
||||||
|
const [totalPages, setTotalPages] = useState<number | undefined>();
|
||||||
|
const [processedPages, setProcessedPages] = useState<number | undefined>();
|
||||||
|
|
||||||
|
const postMessage = (type: string, data?: unknown) => {
|
||||||
|
parent.postMessage({ pluginMessage: { type, data } }, '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMessage = (event: MessageEvent<{ pluginMessage?: PluginMessage }>) => {
|
||||||
|
if (!event.data.pluginMessage) return;
|
||||||
|
|
||||||
|
const { pluginMessage } = event.data;
|
||||||
|
|
||||||
|
switch (pluginMessage.type) {
|
||||||
|
case 'PENPOT_DOCUMENT': {
|
||||||
|
setDownloading(true);
|
||||||
|
|
||||||
|
const file = parse(pluginMessage.data);
|
||||||
|
|
||||||
|
file.export();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'CUSTOM_FONTS': {
|
||||||
|
setMissingFonts(pluginMessage.data);
|
||||||
|
setLoading(false);
|
||||||
|
setNeedsReload(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'CHANGES_DETECTED': {
|
||||||
|
setNeedsReload(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'PROGRESS_NODE': {
|
||||||
|
setCurrentNode(pluginMessage.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'PROGRESS_TOTAL_PAGES': {
|
||||||
|
setTotalPages(pluginMessage.data);
|
||||||
|
setProcessedPages(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'PROGRESS_PROCESSED_PAGES': {
|
||||||
|
setProcessedPages(pluginMessage.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const reload = () => {
|
||||||
|
setLoading(true);
|
||||||
|
postMessage('reload');
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
postMessage('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportPenpot = (data: FormValues) => {
|
||||||
|
setExporting(true);
|
||||||
|
|
||||||
|
postMessage('export', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('message', onMessage);
|
||||||
|
|
||||||
|
postMessage('ready');
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('message', onMessage);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
missingFonts,
|
||||||
|
needsReload,
|
||||||
|
loading,
|
||||||
|
exporting,
|
||||||
|
downloading,
|
||||||
|
currentNode,
|
||||||
|
totalPages,
|
||||||
|
processedPages,
|
||||||
|
reload,
|
||||||
|
cancel,
|
||||||
|
exportPenpot
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in a new issue