Skip to content

Commit 5fe6d96

Browse files
committed
Add copy and share buttons
1 parent 121e372 commit 5fe6d96

File tree

4 files changed

+107
-15
lines changed

4 files changed

+107
-15
lines changed

src/components/RunicEditor/index.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ collapse the details tag. */
152152
}
153153

154154
.runic-editor__download-group {
155-
margin-top: 2em;
155+
margin-top: 1em;
156156
display: flex;
157157
justify-content: center;
158158
align-items: center;

src/components/RunicEditor/index.tsx

+66-14
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { h, Component, VNode } from "preact";
44
import { RuneSVG } from "components/RuneSVG";
55
import { translateSentence } from "src/ipa";
66
import { RangeInput } from "components/RangeInput";
7-
import { downloadURI, drawSVGToCanvas, svgToUri } from "./utils";
7+
import { downloadURI, drawSVGToCanvas, svgToImageBlob, svgToUri } from "./utils";
88
import {
99
CenterAlignIcon,
10+
CopyIcon,
1011
DownloadIcon,
1112
LeftAlignIcon,
1213
RightAlignIcon,
14+
ShareIcon,
1315
} from "components/icons";
1416
import { ChipSelect } from "components/ChipSelect";
1517
import { ColorInput } from "components/ColorInput";
@@ -120,6 +122,20 @@ function getSettings(obj: RunicEditor): VNode {
120122
<DownloadIcon /> JPEG
121123
</button>
122124
</div>
125+
<div className="runic-editor__download-group">
126+
<button
127+
className="runic-editor__download-button"
128+
onClick={() => obj.copyAsPNG()}
129+
>
130+
<CopyIcon /> Copy
131+
</button>
132+
<button
133+
className="runic-editor__download-button"
134+
onClick={() => obj.sharePNG()}
135+
>
136+
<ShareIcon /> Share
137+
</button>
138+
</div>
123139
</div>
124140
);
125141
}
@@ -185,9 +201,38 @@ export class RunicEditor extends Component<Props, State> {
185201
);
186202
};
187203

188-
// Miscellaneous
204+
copyAsPNG = async () => {
205+
this.usePreparedSVG(async (svgElement: SVGElement) => {
206+
const blob = await svgToImageBlob(svgElement);
189207

190-
download = async (format: "svg" | "png" | "jpeg") => {
208+
await navigator.clipboard.write?.([
209+
new ClipboardItem({
210+
"image/png": blob,
211+
}),
212+
]);
213+
});
214+
};
215+
216+
sharePNG = async () => {
217+
this.usePreparedSVG(async (svgElement: SVGElement) => {
218+
const blob = await svgToImageBlob(svgElement);
219+
220+
const file = new File([blob], "rune.png", {
221+
lastModified: Date.now(),
222+
type: "image/png",
223+
});
224+
225+
const shareData: ShareData = {
226+
files: [file],
227+
};
228+
229+
if (navigator.canShare?.(shareData)) {
230+
navigator.share(shareData);
231+
}
232+
});
233+
};
234+
235+
usePreparedSVG = (callback: (svgElement: SVGElement) => void) => {
191236
const svgElement = this.runeSVGElement.svgElement;
192237

193238
// Add background color (only if transparentBackground is disabled)
@@ -196,22 +241,29 @@ export class RunicEditor extends Component<Props, State> {
196241
svgElement.style.setProperty("background-color", backgroundColor);
197242
}
198243

199-
const filename = "rune";
200-
201-
if (format === "svg") {
202-
const uri = svgToUri(svgElement);
203-
downloadURI(uri, `${filename}.svg`);
204-
} else {
205-
// Draw the svg with styles to canvas, then download
206-
const canvas = await drawSVGToCanvas(svgElement);
207-
const uri = canvas.toDataURL(`image/${format}`);
208-
downloadURI(uri, `${filename}.${format}`);
209-
}
244+
callback(svgElement);
210245

211246
// Remove background color
212247
svgElement.style.removeProperty("background-color");
213248
};
214249

250+
// Miscellaneous
251+
252+
download = async (format: "svg" | "png" | "jpeg") => {
253+
this.usePreparedSVG(async (svgElement: SVGElement) => {
254+
const filename = "rune";
255+
if (format === "svg") {
256+
const uri = svgToUri(svgElement);
257+
downloadURI(uri, `${filename}.svg`);
258+
} else {
259+
// Draw the svg with styles to canvas, then download
260+
const canvas = await drawSVGToCanvas(svgElement);
261+
const uri = canvas.toDataURL(`image/${format}`);
262+
downloadURI(uri, `${filename}.${format}`);
263+
}
264+
});
265+
};
266+
215267
render() {
216268
return (
217269
<div className="runic-editor">

src/components/RunicEditor/utils.ts

+12
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ export async function drawSVGToCanvas(
4545
});
4646
}
4747

48+
export async function svgToImageBlob(svgElement: SVGElement) {
49+
const canvas = await drawSVGToCanvas(svgElement);
50+
let resultBlob = null;
51+
await new Promise((resolve) => {
52+
canvas.toBlob((blob) => {
53+
resultBlob = blob;
54+
resolve(0);
55+
});
56+
});
57+
return resultBlob;
58+
}
59+
4860
export function downloadURI(uri: string, filename: string = "tempfilename") {
4961
let link = document.createElement("a");
5062
link.download = filename;

src/components/icons.tsx

+28
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,31 @@ export const EnterKeyIcon = (props: preact.JSX.HTMLAttributes) => (
244244
<path d="M19,6a1,1,0,0,0-1,1v4a1,1,0,0,1-1,1H7.41l1.3-1.29A1,1,0,0,0,7.29,9.29l-3,3a1,1,0,0,0-.21.33,1,1,0,0,0,0,.76,1,1,0,0,0,.21.33l3,3a1,1,0,0,0,1.42,0,1,1,0,0,0,0-1.42L7.41,14H17a3,3,0,0,0,3-3V7A1,1,0,0,0,19,6Z" />
245245
</svg>
246246
);
247+
248+
export const ShareIcon = (props: preact.JSX.HTMLAttributes) => (
249+
// @ts-ignore - HTML attributes are valid for SVG
250+
<svg
251+
width="800px"
252+
height="800px"
253+
viewBox="0 0 24 24"
254+
fill="currentColor"
255+
xmlns="http://www.w3.org/2000/svg"
256+
{...props}
257+
>
258+
<path d="M18,14a4,4,0,0,0-3.08,1.48l-5.1-2.35a3.64,3.64,0,0,0,0-2.26l5.1-2.35A4,4,0,1,0,14,6a4.17,4.17,0,0,0,.07.71L8.79,9.14a4,4,0,1,0,0,5.72l5.28,2.43A4.17,4.17,0,0,0,14,18a4,4,0,1,0,4-4ZM18,4a2,2,0,1,1-2,2A2,2,0,0,1,18,4ZM6,14a2,2,0,1,1,2-2A2,2,0,0,1,6,14Zm12,6a2,2,0,1,1,2-2A2,2,0,0,1,18,20Z" />
259+
</svg>
260+
);
261+
262+
export const CopyIcon = (props: preact.JSX.HTMLAttributes) => (
263+
// @ts-ignore - HTML attributes are valid for SVG
264+
<svg
265+
width="800px"
266+
height="800px"
267+
viewBox="0 0 24 24"
268+
fill="currentColor"
269+
xmlns="http://www.w3.org/2000/svg"
270+
{...props}
271+
>
272+
<path d="M21,8.94a1.31,1.31,0,0,0-.06-.27l0-.09a1.07,1.07,0,0,0-.19-.28h0l-6-6h0a1.07,1.07,0,0,0-.28-.19.32.32,0,0,0-.09,0A.88.88,0,0,0,14.05,2H10A3,3,0,0,0,7,5V6H6A3,3,0,0,0,3,9V19a3,3,0,0,0,3,3h8a3,3,0,0,0,3-3V18h1a3,3,0,0,0,3-3V9S21,9,21,8.94ZM15,5.41,17.59,8H16a1,1,0,0,1-1-1ZM15,19a1,1,0,0,1-1,1H6a1,1,0,0,1-1-1V9A1,1,0,0,1,6,8H7v7a3,3,0,0,0,3,3h5Zm4-4a1,1,0,0,1-1,1H10a1,1,0,0,1-1-1V5a1,1,0,0,1,1-1h3V7a3,3,0,0,0,3,3h3Z" />
273+
</svg>
274+
);

0 commit comments

Comments
 (0)