on master help sign in
add sigil download on passport
11f2c4cLiam Fitzgerald
src/components/Passport.js+8−2
@@ -51,7 +51,13 @@ const buildKeyType = permissions => {
51 * ticket is null | boolean
51 * ticket is null | boolean
52 * address is null | Maybe<string>
52 * address is null | Maybe<string>
53 */
53 */
54−function Passport({ point, className, ticket = null, address = Nothing() }) {
54+function Passport({
55+ point,
56+ className,
57+ ticket = null,
58+ address = Nothing(),
59+ onClick,
60+}) {
55 const birthday = usePointBirthday(point.getOrElse(null));
61 const birthday = usePointBirthday(point.getOrElse(null));
56 const visualBirthday = matchBlinkyDate(birthday);
62 const visualBirthday = matchBlinkyDate(birthday);
57
63
@@ -73,7 +79,7 @@ function Passport({ point, className, ticket = null, address = Nothing() }) {
73 })}${ADDRESS_BUFFER_CHARS}`;
79 })}${ADDRESS_BUFFER_CHARS}`;
74
80
75 return (
81 return (
76− <Grid gap={4} className={cn('bg-black r8 p7', className)}>
82+ <Grid onClick={onClick} gap={4} className={cn('bg-black r8 p7', className)}>
77 <Grid.Item cols={[1, 4]}>
83 <Grid.Item cols={[1, 4]}>
78 <MaybeSigil patp={name} size={64} />
84 <MaybeSigil patp={name} size={64} />
79 </Grid.Item>
85 </Grid.Item>
src/components/SigilDownloader.jsnew file+61
@@ -0,0 +1,61 @@
1+import React, { useRef } from 'react';
2+import * as ob from 'urbit-ob';
3+import { saveAs } from 'file-saver';
4+import { sigil, stringRenderer } from 'urbit-sigil-js';
5+
6+import { dataURItoBlob, loadImg, initCanvas } from 'lib/SigilDownloader';
7+
8+// const attachClickHandler = (children, handler) => Array.isArray(children) ? children.map((Child,key) => (<Child key={key} onClick={handler} />)) : (<children onClick={handler}>);
9+export default function SigilDownloader({ point, children }) {
10+ const downloadSigil = () => {
11+ const patp = ob.patp(point);
12+
13+ const _size = 1024 / window.devicePixelRatio;
14+ const pngSize = 1024;
15+
16+ const DATA_URI_PREFIX = 'data:image/svg+xml;base64,';
17+
18+ // initialize canvas element
19+ const canvas = initCanvas(canvasRef.current, { x: _size, y: _size });
20+ const ctx = canvas.getContext('2d');
21+
22+ // make sigil svg and encoded into base64
23+ const svg = sigil({
24+ patp,
25+ full: true,
26+ renderer: stringRenderer,
27+ size: _size,
28+ colors: ['#FFFFFF', '#000000'],
29+ margin: (54 / 256) * _size,
30+ });
31+
32+ // FF rendering hack
33+ const svgDocument = new DOMParser().parseFromString(svg, 'image/svg+xml');
34+ svgDocument.documentElement.width.baseVal.valueAsString = `${pngSize}px`;
35+ svgDocument.documentElement.height.baseVal.valueAsString = `${pngSize}px`;
36+ const svgText = new XMLSerializer().serializeToString(svgDocument);
37+ const svg64 = btoa(svgText);
38+
39+ const svgBlob = new Blob([svgText], { type: 'image/svg+xml' });
40+ saveAs(svgBlob, `${patp}.svg`);
41+
42+ const pngTask = loadImg(DATA_URI_PREFIX + svg64, _size, _size).then(img => {
43+ ctx.drawImage(img, 0, 0, _size, _size);
44+ const png = dataURItoBlob(canvas.toDataURL('image/png'));
45+ saveAs(png, `${patp}.png`);
46+ ctx.clearRect(0, 0, pngSize, pngSize);
47+ });
48+
49+ return pngTask;
50+ };
51+ const canvasRef = useRef(null);
52+
53+ return (
54+ <>
55+ {React.Children.map(children, child =>
56+ React.cloneElement(child, { onClick: downloadSigil })
57+ )}
58+ <canvas style={{ display: 'none' }} ref={canvasRef} />
59+ </>
60+ );
61+}
src/lib/SigilDownloader.jsnew file+46
@@ -0,0 +1,46 @@
1+export const initCanvas = (canvas, size) => {
2+ const { x, y } = size;
3+ const ctx = canvas.getContext('2d');
4+
5+ let ratio = window.devicePixelRatio || 1;
6+
7+ canvas.width = x * ratio;
8+ canvas.height = y * ratio;
9+ canvas.style.width = x + 'px';
10+ canvas.style.height = y + 'px';
11+
12+ ctx.scale(ratio, ratio);
13+
14+ return canvas;
15+};
16+
17+export const loadImg = src =>
18+ new Promise((resolve, reject) => {
19+ const img = new Image();
20+ debugger;
21+ img.onload = () => resolve(img);
22+ img.onerror = () => reject(`Error loading image. src: ${src}`);
23+ img.src = src;
24+ });
25+
26+export const dataURItoBlob = dataURI => {
27+ // convert base64 to raw binary data held in a string
28+ // doesn't handle URLEncoded DataURIs
29+ const byteString = atob(dataURI.split(',')[1]);
30+ // separate out the mime component
31+ const mimeString = dataURI
32+ .split(',')[0]
33+ .split(':')[1]
34+ .split(';')[0];
35+ // write the bytes of the string to an ArrayBuffer
36+ const ab = new ArrayBuffer(byteString.length);
37+ // create a view into the buffer
38+ let ia = new Uint8Array(ab);
39+ // set the bytes of the buffer to the correct values
40+ for (var i = 0; i < byteString.length; i++) {
41+ ia[i] = byteString.charCodeAt(i);
42+ }
43+ // write the ArrayBuffer to a blob, and you're done
44+ const blob = new Blob([ab], { type: mimeString });
45+ return blob;
46+};
src/views/Point.js+5−2
@@ -1,6 +1,5 @@
1import React, { useCallback } from 'react';
1import React, { useCallback } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3−import * as need from 'lib/need';
4import { Grid } from 'indigo-react';
3import { Grid } from 'indigo-react';
5import { azimuth } from 'azimuth-js';
4import { azimuth } from 'azimuth-js';
6
5
@@ -11,7 +10,9 @@ import Greeting from 'components/Greeting';
11import Passport from 'components/Passport';
10import Passport from 'components/Passport';
12import { ForwardButton, BootArvoButton } from 'components/Buttons';
11import { ForwardButton, BootArvoButton } from 'components/Buttons';
13import { matchBlinky } from 'components/Blinky';
12import { matchBlinky } from 'components/Blinky';
13+import SigilDownloader from 'components/SigilDownloader';
14
14
15+import * as need from 'lib/need';
15import useInvites from 'lib/useInvites';
16import useInvites from 'lib/useInvites';
16import { useSyncOwnedPoints } from 'lib/useSyncPoints';
17import { useSyncOwnedPoints } from 'lib/useSyncPoints';
17import useCurrentPermissions from 'lib/useCurrentPermissions';
18import useCurrentPermissions from 'lib/useCurrentPermissions';
@@ -89,7 +90,9 @@ export default function Point() {
89 return (
90 return (
90 <View pop={pop} inset>
91 <View pop={pop} inset>
91 <Greeting point={point} />
92 <Greeting point={point} />
93+ <SigilDownloader point={point}>
92− <Passport point={Just(point)} />
94+ <Passport point={Just(point)} />
95+ </SigilDownloader>
93 <Grid className="pt2">
96 <Grid className="pt2">
94 {inviteButton}
97 {inviteButton}
95 <Grid.Item
98 <Grid.Item