Skip to content

Commit c159ecc

Browse files
authored
Async loading (#174)
* added loading message when processing large images and paths. still has issues with async because messages aren't sent synchronously * Moved the loading message out of the implementation folder and into the library. Improved the visuals and language of the loading message. * Fixed two minor linter errors in readme
1 parent 7456e72 commit c159ecc

File tree

8 files changed

+61
-28
lines changed

8 files changed

+61
-28
lines changed

apps/debug/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## Getting Started
1+
# Getting Started
22

33
First, run the development server:
44

apps/plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## Getting Started
1+
# Getting Started
22

33
First, run the development server:
44

apps/plugin/ui-src/App.tsx

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function App() {
3030
const [state, setState] = useState<AppState>({
3131
code: "",
3232
selectedFramework: "HTML",
33-
isLoading: false,
33+
isLoading: true,
3434
htmlPreview: emptyPreview,
3535
settings: null,
3636
colors: [],
@@ -49,12 +49,21 @@ export default function App() {
4949
console.log("[ui] message received:", untypedMessage);
5050

5151
switch (untypedMessage.type) {
52+
case "conversionStart":
53+
setState((prevState) => ({
54+
...prevState,
55+
code: "",
56+
isLoading: true,
57+
}));
58+
break;
59+
5260
case "code":
5361
const conversionMessage = untypedMessage as ConversionMessage;
5462
setState((prevState) => ({
5563
...prevState,
5664
...conversionMessage,
5765
selectedFramework: conversionMessage.settings.framework,
66+
isLoading: false,
5867
}));
5968
break;
6069

@@ -76,6 +85,7 @@ export default function App() {
7685
warnings: [],
7786
colors: [],
7887
gradients: [],
88+
isLoading: false,
7989
}));
8090
break;
8191

@@ -87,6 +97,7 @@ export default function App() {
8797
colors: [],
8898
gradients: [],
8999
code: `Error :(\n// ${errorMessage.error}`,
100+
isLoading: false,
90101
}));
91102
break;
92103
default:
@@ -99,26 +110,6 @@ export default function App() {
99110
};
100111
}, []);
101112

102-
useEffect(() => {
103-
if (state.selectedFramework === null) {
104-
const timer = setTimeout(
105-
() => setState((prevState) => ({ ...prevState, isLoading: true })),
106-
300,
107-
);
108-
return () => clearTimeout(timer);
109-
} else {
110-
setState((prevState) => ({ ...prevState, isLoading: false }));
111-
}
112-
}, [state.selectedFramework]);
113-
114-
if (state.selectedFramework === null) {
115-
return state.isLoading ? (
116-
<div className="w-full h-96 justify-center text-center items-center dark:text-white text-lg">
117-
Loading Plugin...
118-
</div>
119-
) : null;
120-
}
121-
122113
const handleFrameworkChange = (updatedFramework: Framework) => {
123114
setState((prevState) => ({
124115
...prevState,
@@ -129,11 +120,13 @@ export default function App() {
129120
targetOrigin: "*",
130121
});
131122
};
132-
console.log("state.code", state.code.slice(0, 25));
123+
124+
const darkMode = figmaColorBgValue !== "#ffffff";
133125

134126
return (
135-
<div className={`${figmaColorBgValue === "#ffffff" ? "" : "dark"}`}>
127+
<div className={`${darkMode ? "dark" : ""}`}>
136128
<PluginUI
129+
isLoading={state.isLoading}
137130
code={state.code}
138131
warnings={state.warnings}
139132
selectedFramework={state.selectedFramework}

packages/backend/src/code.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import {
33
retrieveGenericSolidUIColors,
44
retrieveGenericLinearGradients as retrieveGenericGradients,
55
} from "./common/retrieveUI/retrieveColors";
6-
import { generateHTMLPreview, htmlMain } from "./html/htmlMain";
7-
import { postConversionComplete, postEmptyMessage } from "./messaging";
86
import {
97
addWarning,
108
clearWarnings,
119
warnings,
1210
} from "./common/commonConversionWarnings";
11+
import { generateHTMLPreview } from "./html/htmlMain";
12+
import {
13+
postConversionComplete,
14+
postConversionStart,
15+
postEmptyMessage,
16+
} from "./messaging";
1317
import { PluginSettings } from "types";
1418
import { convertToCode } from "./common/retrieveUI/convertToCode";
1519

@@ -24,6 +28,10 @@ export const run = async (settings: PluginSettings) => {
2428
);
2529
}
2630

31+
postConversionStart();
32+
// force postMessage to run right now.
33+
await new Promise((resolve) => setTimeout(resolve, 30));
34+
2735
const convertedSelection = convertNodesToAltNodes(selection, null);
2836

2937
// ignore when nothing was selected

packages/backend/src/messaging.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
ConversionMessage,
3+
ConversionStartMessage,
34
EmptyMessage,
45
ErrorMessage,
56
PluginSettings,
@@ -11,6 +12,9 @@ export const postBackendMessage = figma.ui.postMessage;
1112
export const postEmptyMessage = () =>
1213
postBackendMessage({ type: "empty" } as EmptyMessage);
1314

15+
export const postConversionStart = () =>
16+
postBackendMessage({ type: "conversionStart" } as ConversionStartMessage);
17+
1418
export const postConversionComplete = (
1519
conversionData: ConversionMessage | Omit<ConversionMessage, "type">,
1620
) => postBackendMessage({ ...conversionData, type: "code" });

packages/plugin-ui/src/PluginUI.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useState } from "react";
21
import copy from "copy-to-clipboard";
32
import Preview from "./components/Preview";
43
import GradientsPanel from "./components/GradientsPanel";
@@ -17,6 +16,7 @@ import {
1716
preferenceOptions,
1817
selectPreferenceOptions,
1918
} from "./codegenPreferenceOptions";
19+
import Loading from "./components/Loading";
2020

2121
type PluginUIProps = {
2222
code: string;
@@ -28,11 +28,14 @@ type PluginUIProps = {
2828
onPreferenceChanged: (key: string, value: boolean | string) => void;
2929
colors: SolidColorConversion[];
3030
gradients: LinearGradientConversion[];
31+
isLoading: boolean;
3132
};
3233

3334
const frameworks: Framework[] = ["HTML", "Tailwind", "Flutter", "SwiftUI"];
3435

3536
export const PluginUI = (props: PluginUIProps) => {
37+
if (props.isLoading) return <Loading />;
38+
3639
const isEmpty = props.code === "";
3740

3841
const warnings = props.warnings ?? [];
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
interface LoadingProps {}
2+
const Loading = (_props: LoadingProps) => (
3+
<div
4+
style={{ animation: "fadeIn 600ms" }}
5+
className={`flex w-full h-full p-4 dark:text-white text-lg`}
6+
>
7+
<div className="dark:text-white">
8+
<p className="text-lg font-medium dark:text-white rounded-lg">
9+
Converting...
10+
</p>
11+
<p className="text-xs italic max-w-56">
12+
This can take a while if the selection has many images or paths
13+
</p>
14+
</div>
15+
<style>{`
16+
@keyframes fadeIn {
17+
0% { opacity: 0; }
18+
25% {opacity: 0; }
19+
100% { opacity: 1; }
20+
}
21+
`}</style>
22+
</div>
23+
);
24+
export default Loading;

packages/types/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface UIMessage {
4747
pluginMessage: Message;
4848
}
4949
export type EmptyMessage = Message & { type: "empty" };
50+
export type ConversionStartMessage = Message & { type: "conversionStarted" };
5051
export type ConversionMessage = Message & {
5152
type: "code";
5253
} & ConversionData;

0 commit comments

Comments
 (0)