Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Export Styles] Add download button to variable export #28

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/export-styles/ui-src/App.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#root,
.appRoot,
.saltFlexLayout {
.viewRoot {
height: 100%;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import userEvents from "@testing-library/user-event";
import React from "react";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { VariableJsonView } from "../views/VariableJsonView";
import * as utilExports from "../components/utils";

// Mock out download, `URL.createObjectURL` not supported in test env
vi.spyOn(utilExports, "downloadBlob").mockImplementation(() => {});

describe("VariableJsonView", () => {
beforeEach(() => {
Expand Down Expand Up @@ -161,4 +165,30 @@ describe("VariableJsonView", () => {
).toEqual("");
expect(screen.getByRole("button", { name: "Export" })).toBeDisabled();
});
test("download JSON button is disabled until export text is available", async () => {
const downloadJsonButton = screen.getByRole("button", {
name: "Download JSON",
});
expect(downloadJsonButton).toHaveAttribute("aria-disabled", "true");

const fileName = "filename.json";
const expectedText = "text to be filled in";
fireEvent(
window,
new MessageEvent("message", {
data: {
pluginMessage: {
type: "export-variable-to-json-result",
body: expectedText,
fileName: fileName,
},
},
})
);
expect(downloadJsonButton).not.toHaveAttribute("aria-disabled", "true");

await userEvents.click(downloadJsonButton);

expect(utilExports.downloadBlob).toHaveBeenCalledOnce();
});
});
29 changes: 29 additions & 0 deletions packages/export-styles/ui-src/components/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const downloadDataUri = (dataUri: string, fileName: string) => {
const link = document.createElement("a");
link.href = dataUri;
link.download = fileName;
// some browser needs the anchor to be in the doc
document.body.append(link);
link.click();
link.remove();
// in case the Blob uses a log of memory
setTimeout(() => {
URL.revokeObjectURL(link.href);
}, 7000);
};

/**
* This is much more efficient than manually converting a Blob or Uint8Array (e.g. `new Blob([uint8Array])`) to string / dataUri.
*/
export const downloadBlob = (blob: Blob, fileName: string) => {
const link = document.createElement("a");
// create a blobURI pointing to our Blob
link.href = URL.createObjectURL(blob);
link.download = fileName;
// some browser needs the anchor to be in the doc
document.body.append(link);
link.click();
link.remove();
// in case the Blob uses a lot of memory
setTimeout(() => URL.revokeObjectURL(link.href), 7000);
};
2 changes: 1 addition & 1 deletion packages/export-styles/ui-src/views/ExportCssView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const ExportCssView = () => {
}, []);

return (
<StackLayout gap={1}>
<StackLayout gap={1} className="viewRoot">
<FormField label="Prefix" labelPlacement="left">
<Input
value={prefix}
Expand Down
2 changes: 1 addition & 1 deletion packages/export-styles/ui-src/views/ExportJsonView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const ExportJsonView = () => {
}, []);

return (
<StackLayout gap={1}>
<StackLayout gap={1} className="viewRoot">
<FormField label="Format" labelPlacement="left">
<Dropdown
source={ExportColorAllFormats}
Expand Down
33 changes: 26 additions & 7 deletions packages/export-styles/ui-src/views/VariableJsonView.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Button, StackLayout, Text } from "@salt-ds/core";
import { Button, FlexLayout, StackLayout, Text, Tooltip } from "@salt-ds/core";
import { DownloadIcon } from "@salt-ds/icons";
import { Dropdown, FormField } from "@salt-ds/lab";
import React, { useEffect, useId, useRef, useState } from "react";
import {
ExportColorAllFormats,
ExportColorFormat,
FigmaVariableCollection,
FigmaVariableMode,
PostToFigmaMessage,
} from "../../shared-src";
import { downloadBlob } from "../components/utils";
import { FigmaToUIMessageEvent } from "../types";

export const VariableJsonView = () => {
Expand Down Expand Up @@ -87,8 +87,14 @@ export const VariableJsonView = () => {
};
}, []);

const onDownload = () => {
var blob = new Blob([text], { type: "application/json" });
// Use blob instead of plain text downloadDataUri, as Safari wipes out new line
downloadBlob(blob, fileName);
};

return (
<StackLayout gap={1}>
<StackLayout gap={1} className="viewRoot">
<FormField label="Collection">
<Dropdown
source={collections}
Expand Down Expand Up @@ -137,9 +143,22 @@ export const VariableJsonView = () => {
spellCheck={false}
aria-labelledby={exportLabel}
></textarea>
<Button onClick={onCopy} ref={copyButtonRef}>
Copy
</Button>
<FlexLayout gap={1}>
<Button onClick={onCopy} ref={copyButtonRef} style={{ flex: 1 }}>
Copy
</Button>

<Tooltip placement="top" content="Download data as JSON">
<Button
focusableWhenDisabled
onClick={onDownload}
disabled={!fileName}
aria-label="Download JSON"
>
<DownloadIcon />
</Button>
</Tooltip>
</FlexLayout>
</StackLayout>
);
};