Skip to content

Commit

Permalink
Refactor UI code so it does not fail when exporting (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
jordisala1991 authored Jun 4, 2024
1 parent 7895daa commit df30dfd
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 156 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 16 additions & 13 deletions ui-src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,18 +22,20 @@ export const App = () => {
}, [height]);

return (
<Wrapper ref={ref} overflowing={(height ?? 0) > MAX_HEIGHT}>
<Stack>
<Penpot
style={{
alignSelf: 'center',
height: 'auto',
width: '8.125rem',
fill: 'var(--figma-color-icon)'
}}
/>
<PenpotExporter />
</Stack>
</Wrapper>
<FigmaProvider>
<Wrapper ref={ref} overflowing={(height ?? 0) > MAX_HEIGHT}>
<Stack>
<Penpot
style={{
alignSelf: 'center',
height: 'auto',
width: '8.125rem',
fill: 'var(--figma-color-icon)'
}}
/>
<PenpotExporter />
</Stack>
</Wrapper>
</FigmaProvider>
);
};
32 changes: 32 additions & 0 deletions ui-src/components/ExportForm.tsx
Original file line number Diff line number Diff line change
@@ -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<string, string>;

export const ExportForm = () => {
const { cancel, exportPenpot } = useFigma();
const methods = useForm<FormValues>();

return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(exportPenpot)}>
<Stack>
<MissingFontsSection />
<Stack space="xsmall" direction="row">
<Button type="submit" fullWidth>
Export to Penpot
</Button>
<Button secondary onClick={cancel} fullWidth>
Cancel
</Button>
</Stack>
</Stack>
</form>
</FormProvider>
);
};
32 changes: 4 additions & 28 deletions ui-src/components/ExporterProgress.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,11 @@
import { LoadingIndicator } from '@create-figma-plugin/ui';
import { useEffect, useState } from 'react';

import { Stack } from './Stack';

type ExporterProgressProps = {
downloading: boolean;
};

export const ExporterProgress = ({ downloading }: ExporterProgressProps) => {
const [currentNode, setCurrentNode] = useState<string | undefined>();
const [totalPages, setTotalPages] = useState<number | undefined>();
const [processedPages, setProcessedPages] = useState<number | undefined>();
import { useFigma } from '@ui/context';

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);
import { Stack } from './Stack';

return () => {
window.removeEventListener('message', onMessage);
};
}, []);
export const ExporterProgress = () => {
const { currentNode, totalPages, processedPages, downloading } = useFigma();

const truncateText = (text: string, maxChars: number) => {
if (text.length <= maxChars) {
Expand Down
14 changes: 7 additions & 7 deletions ui-src/components/MissingFontsSection.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Stack space="small">
<Stack space="xsmall">
<Banner icon={<IconInfo32 />}>
{fonts.length} custom font{fonts.length > 1 ? 's' : ''} detected
{missingFonts.length} custom font{missingFonts.length > 1 ? 's' : ''} detected
</Banner>
<span>To export your file with custom fonts, please follow these steps:</span>
<Stack as="ol" space="xsmall" style={{ paddingLeft: '1rem' }}>
Expand Down Expand Up @@ -43,7 +43,7 @@ export const MissingFontsSection = ({ fonts }: MissingFontsSectionProps) => {
<li>Return here and paste the font IDs in the section below</li>
</Stack>
</Stack>
{fonts.map(font => (
{missingFonts.map(font => (
<Stack space="2xsmall" key={font}>
<ControlledTextbox name={font} placeholder="Paste font ID from Penpot" />
<span>{font}</span>
Expand Down
111 changes: 8 additions & 103 deletions ui-src/components/PenpotExporter.tsx
Original file line number Diff line number Diff line change
@@ -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<string, string>;
import { PluginReload } from './PluginReload';

export const PenpotExporter = () => {
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 methods = useForm<FormValues>();

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 <LoadingIndicator />;

if (exporting) return <ExporterProgress downloading={downloading} />;
if (exporting) return <ExporterProgress />;

if (needsReload) {
return (
<Stack space="small">
<Banner icon={<IconInfo32 />}>
Changes detected. Please reload the plug-in to ensure all modifications are included in
the exported file.
</Banner>
<Stack space="xsmall" direction="row">
<Button onClick={reload} fullWidth>
Reload
</Button>
<Button secondary onClick={cancel} fullWidth>
Cancel
</Button>
</Stack>
</Stack>
);
}
if (needsReload) return <PluginReload />;

return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(exportPenpot)}>
<Stack>
<MissingFontsSection fonts={missingFonts} />
<Stack space="xsmall" direction="row">
<Button type="submit" fullWidth>
Export to Penpot
</Button>
<Button secondary onClick={cancel} fullWidth>
Cancel
</Button>
</Stack>
</Stack>
</form>
</FormProvider>
);
return <ExportForm />;
};
25 changes: 25 additions & 0 deletions ui-src/components/PluginReload.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Stack space="small">
<Banner icon={<IconInfo32 />}>
Changes detected. Please reload the plug-in to ensure all modifications are included in the
exported file.
</Banner>
<Stack space="xsmall" direction="row">
<Button onClick={reload} fullWidth>
Reload
</Button>
<Button secondary onClick={cancel} fullWidth>
Cancel
</Button>
</Stack>
</Stack>
);
};
Loading

0 comments on commit df30dfd

Please sign in to comment.