Skip to content

Commit 03c50bf

Browse files
committed
feat(plugin): persist plugin settings between sessions
1 parent 98e78f4 commit 03c50bf

File tree

9 files changed

+175
-21
lines changed

9 files changed

+175
-21
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "phosphor-figma",
3-
"version": "2.0.1",
3+
"version": "2.0.2",
44
"license": "MIT",
55
"homepage": "https://phosphoricons.com",
66
"author": {
@@ -45,7 +45,7 @@
4545
},
4646
"dependencies": {
4747
"@phosphor-icons/core": "^2.0.2",
48-
"@phosphor-icons/react": "^2.0.4",
48+
"@phosphor-icons/react": "^2.0.5",
4949
"fuse.js": "^6.6.2",
5050
"prop-types": "^15.8.1",
5151
"react": "^17",

src/components/IconGrid/IconGrid.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useRecoilValue } from "recoil";
44
import { IconContext, SmileyXEyes } from "@phosphor-icons/react";
55

66
import { IconEntry } from "../../lib";
7+
import { MessageType } from "../../types";
78

89
import {
910
iconWeightAtom,
@@ -42,7 +43,7 @@ const IconGrid: React.FC<{}> = () => {
4243
parent.postMessage(
4344
{
4445
pluginMessage: {
45-
type: "insert",
46+
type: MessageType.INSERT,
4647
payload: { name, pascal_name, svg, weight, flatten },
4748
},
4849
},
@@ -81,7 +82,10 @@ const IconGrid: React.FC<{}> = () => {
8182
offset: dragStartRef.current,
8283
};
8384

84-
parent.postMessage({ pluginMessage: { type: "drop", payload } }, "*");
85+
parent.postMessage(
86+
{ pluginMessage: { type: MessageType.DROP, payload } },
87+
"*"
88+
);
8589
},
8690
[weight, flatten]
8791
);

src/main.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { CUSTOM_NODE_KEY, DEFAULT_SIZE } from "./constants";
2-
import { DropPayload, IconPayload, Message } from "./types";
2+
import {
3+
DropPayload,
4+
IconPayload,
5+
GetAsyncPayload,
6+
SetAsyncPayload,
7+
Message,
8+
MessageType,
9+
} from "./types";
310
import { fetchRawIcon, getInjectableNode, getOffsetVector } from "./utils";
411

512
let hasTriedDragAndDrop = false;
@@ -8,14 +15,23 @@ main();
815
function main() {
916
figma.ui.onmessage = ({ type, payload }: Message) => {
1017
switch (type) {
11-
case "insert":
18+
case MessageType.INSERT:
1219
insertIcon(payload);
1320
break;
14-
case "drop":
21+
case MessageType.DROP:
1522
hasTriedDragAndDrop = true;
1623
dropIcon(payload);
1724
break;
18-
case "log":
25+
case MessageType.STORAGE_GET_REQUEST:
26+
getRequest(payload);
27+
break;
28+
case MessageType.STORAGE_SET_REQUEST:
29+
setRequest(payload);
30+
break;
31+
case MessageType.STORAGE_DELETE_REQUEST:
32+
deleteRequest(payload);
33+
break;
34+
case MessageType.LOG:
1935
default:
2036
console.log("Log: ", payload);
2137
}
@@ -24,6 +40,24 @@ function main() {
2440
figma.showUI(__html__, { width: 362, height: 490, themeColors: true });
2541
}
2642

43+
async function getRequest(payload: GetAsyncPayload) {
44+
const value = await figma.clientStorage.getAsync(payload.key);
45+
if (typeof value !== "undefined") {
46+
figma.ui.postMessage({
47+
type: MessageType.STORAGE_GET_RESPONSE,
48+
payload: { key: payload.key, value },
49+
});
50+
}
51+
}
52+
53+
async function setRequest(payload: SetAsyncPayload) {
54+
return figma.clientStorage.setAsync(payload.key, payload.value);
55+
}
56+
57+
async function deleteRequest(payload: GetAsyncPayload) {
58+
return figma.clientStorage.deleteAsync(payload.key);
59+
}
60+
2761
async function insertIcon(payload: IconPayload) {
2862
const svg = payload.flatten ? payload.svg : await fetchRawIcon(payload);
2963

@@ -46,12 +80,12 @@ async function insertIcon(payload: IconPayload) {
4680
injectableNode.appendChild(frame);
4781

4882
figma.currentPage.selection = [frame];
49-
figma.notify(`✅ Added ${payload.pascal_name}`, { timeout: 2000 });
83+
figma.notify(`Inserted ${payload.pascal_name}`, { timeout: 2000 });
5084

5185
if (!hasTriedDragAndDrop) {
5286
setTimeout(() => {
5387
if (!hasTriedDragAndDrop)
54-
figma.notify("💡 Try drag-and-drop too!", { timeout: 4000 });
88+
figma.notify("Try drag-and-drop too!", { timeout: 4000 });
5589
hasTriedDragAndDrop = true;
5690
}, 4000);
5791
}
@@ -81,7 +115,7 @@ async function dropIcon(payload: DropPayload) {
81115
frame.y = bounds.y + yFromCanvas / zoom - offset.y;
82116

83117
figma.currentPage.selection = [frame];
84-
figma.notify(`✅ Added ${pascal_name}`, { timeout: 2000 });
118+
figma.notify(`Inserted ${pascal_name}`, { timeout: 2000 });
85119
}
86120

87121
function ungroup(node: SceneNode, parent: BaseNode & ChildrenMixin) {

src/state/StorageProxy.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { AtomEffect } from "recoil";
2+
3+
import {
4+
GetAsyncPayload,
5+
SetAsyncPayload,
6+
Response,
7+
MessageType,
8+
} from "../types";
9+
10+
type StorageListener = (event: MessageEvent) => void;
11+
12+
export default class StorageProxy {
13+
static listeners: Record<string, StorageListener> = {};
14+
15+
static register<T>({ node, setSelf, onSet }: Parameters<AtomEffect<T>>[0]) {
16+
const listener: StorageListener = (event) => {
17+
const { type, payload } = event.data.pluginMessage as Response<T>;
18+
if (type !== MessageType.STORAGE_GET_RESPONSE || payload.key !== node.key)
19+
return;
20+
setSelf(payload.value);
21+
};
22+
23+
window.addEventListener("message", listener);
24+
25+
onSet((value, _, isReset) => {
26+
if (isReset) {
27+
StorageProxy.requestReset({ key: node.key });
28+
} else {
29+
StorageProxy.requestSet({ key: node.key, value });
30+
}
31+
});
32+
33+
StorageProxy.listeners[node.key] = listener;
34+
35+
StorageProxy.requestGet({ key: node.key });
36+
}
37+
38+
static unregister(key: string) {
39+
window.removeEventListener("message", StorageProxy.listeners[key]);
40+
delete StorageProxy.listeners.key;
41+
}
42+
43+
static requestGet(payload: GetAsyncPayload) {
44+
parent.postMessage(
45+
{
46+
pluginMessage: {
47+
type: MessageType.STORAGE_GET_REQUEST,
48+
payload,
49+
},
50+
},
51+
"*"
52+
);
53+
}
54+
55+
static requestSet<T>(payload: SetAsyncPayload<T>) {
56+
parent.postMessage(
57+
{
58+
pluginMessage: {
59+
type: MessageType.STORAGE_SET_REQUEST,
60+
payload,
61+
},
62+
},
63+
"*"
64+
);
65+
}
66+
67+
static requestReset(payload: GetAsyncPayload) {
68+
parent.postMessage(
69+
{
70+
pluginMessage: {
71+
type: MessageType.STORAGE_DELETE_REQUEST,
72+
payload,
73+
},
74+
},
75+
"*"
76+
);
77+
}
78+
}

src/state/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Fuse from "fuse.js";
44

55
import { IconEntry } from "../lib";
66
import { icons } from "../lib/icons";
7+
import StorageProxy from "./StorageProxy";
78

89
const fuse = new Fuse(icons, {
910
keys: [
@@ -20,6 +21,7 @@ const fuse = new Fuse(icons, {
2021
export const iconWeightAtom = atom<IconStyle>({
2122
key: "iconWeightAtom",
2223
default: IconStyle.REGULAR,
24+
effects: [StorageProxy.register],
2325
});
2426

2527
export const searchQueryAtom = atom<string>({
@@ -30,6 +32,7 @@ export const searchQueryAtom = atom<string>({
3032
export const flattenAtom = atom<boolean>({
3133
key: "flattenAtom",
3234
default: true,
35+
effects: [StorageProxy.register],
3336
});
3437

3538
export const filteredQueryResultsSelector = selector<ReadonlyArray<IconEntry>>({

src/types.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,55 @@ export interface DropPayload extends IconPayload {
1414
windowSize: { width: number; height: number };
1515
}
1616

17-
export type Message =
17+
export interface GetAsyncPayload {
18+
key: string;
19+
}
20+
21+
export interface SetAsyncPayload<T = any> extends GetAsyncPayload {
22+
value: T;
23+
}
24+
25+
export enum MessageType {
26+
INSERT = "icon_insert",
27+
DROP = "icon_drop",
28+
STORAGE_GET_REQUEST = "storage_get_req",
29+
STORAGE_SET_REQUEST = "storage_set_req",
30+
STORAGE_DELETE_REQUEST = "storage_delete_req",
31+
STORAGE_GET_RESPONSE = "storage_get_res",
32+
LOG = "log",
33+
}
34+
35+
export type Message<T = any> =
1836
| {
19-
type: "insert";
37+
type: MessageType.INSERT;
2038
payload: IconPayload;
2139
}
2240
| {
23-
type: "drop";
41+
type: MessageType.DROP;
2442
payload: DropPayload;
2543
}
2644
| {
27-
type: "log";
45+
type: MessageType.STORAGE_GET_REQUEST;
46+
payload: GetAsyncPayload;
47+
}
48+
| {
49+
type: MessageType.STORAGE_SET_REQUEST;
50+
payload: SetAsyncPayload<T>;
51+
}
52+
| {
53+
type: MessageType.STORAGE_DELETE_REQUEST;
54+
payload: GetAsyncPayload;
55+
}
56+
| {
57+
type: MessageType.LOG;
2858
payload?: any;
2959
};
3060

61+
export type Response<T> = {
62+
type: MessageType.STORAGE_GET_RESPONSE;
63+
payload: SetAsyncPayload<T>;
64+
};
65+
3166
export type InjectableNode =
3267
| PageNode
3368
| FrameNode

src/ui.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const App: React.FC<{}> = () => {
1212
<RecoilRoot>
1313
<div className="app">
1414
<Toolbar />
15-
<Suspense fallback={<p>Loading...</p>}>
15+
<Suspense fallback={null}>
1616
<IconGrid />
1717
</Suspense>
1818
<Footer />

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,6 @@ export async function fetchRawIcon(payload: IconPayload): Promise<string> {
7878

7979
return text;
8080
} catch (_) {
81-
figma.notify("Oops! Looks like you're offline.");
81+
figma.notify("Oops! Looks like you're offline.");
8282
}
8383
}

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
2424
resolved "https://registry.yarnpkg.com/@phosphor-icons/core/-/core-2.0.2.tgz#108c0e5d798bcb76951ae8d0a61987cd549ac949"
2525
integrity sha512-ZHvHBagars5C2IGKW9TdiOe8LwIVq6/LmJRfxBlfFcbsNAfYXTNzf6IUQKy0hrPTn6bTdQh5ghe436i+W1JKrw==
2626

27-
"@phosphor-icons/react@^2.0.4":
28-
version "2.0.4"
29-
resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.0.4.tgz#33a9b3feaae0031982e9ebd232448879c547190d"
30-
integrity sha512-43GECcZKPyxFJhpXa9H9U7HXrneR4IxPtXGfUhVehvFE0kQaxI9oyVw4jePsVSn60L9e3WblFplFgGVP/9STdg==
27+
"@phosphor-icons/react@^2.0.5":
28+
version "2.0.5"
29+
resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.0.5.tgz#3c07191a4489ed5a8c35f4bc228938990d13b1a4"
30+
integrity sha512-Ghohi+Dk+Y0RoI1vra3jH2QC3sO6C16a/Km0gfy05ONIsC62Vwn1SK2Qaim77VPyn1FS7qjMxocdi9kx6btvXg==
3131

3232
"@types/html-minifier-terser@^5.0.0":
3333
version "5.1.2"

0 commit comments

Comments
 (0)