Skip to content

Commit 1c7e86f

Browse files
committed
docs - fix share
1 parent 5759401 commit 1c7e86f

File tree

6 files changed

+167
-66
lines changed

6 files changed

+167
-66
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.content {
2+
padding-top: 20px;
3+
}
4+
.copy_line {
5+
display: flex;
6+
gap: 4px;
7+
margin: 20px 0;
8+
min-width: 40vw;
9+
}
10+
.copy_line input {
11+
flex: 1;
12+
}
13+
.buttons {
14+
text-align: center;
15+
}

docs/src/components/CopyDialog.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Modal from "./Modal";
2+
import styles from "./CopyDialog.module.css";
3+
import { useEffect, useRef } from "react";
4+
5+
const CopyDialog = ({
6+
title,
7+
open,
8+
value,
9+
onRequestClose,
10+
children,
11+
}: {
12+
title: string;
13+
open: boolean;
14+
value: string;
15+
onRequestClose: () => any;
16+
children?: any;
17+
}) => {
18+
const inputRef = useRef<HTMLInputElement | undefined>(undefined);
19+
useEffect(() => {
20+
if (open) {
21+
inputRef.current?.focus();
22+
inputRef.current?.select();
23+
}
24+
}, [open]);
25+
26+
if (!open) return null;
27+
return (
28+
<Modal onRequestClose={onRequestClose} title={title} contentClassName={styles.content}>
29+
{children}
30+
<div className={styles.copy_line}>
31+
<input ref={inputRef} defaultValue={value} readOnly />
32+
<button
33+
className="button button--primary button--sm share-button shadow--lw"
34+
onClick={() => {
35+
navigator.clipboard.writeText(value);
36+
}}
37+
>
38+
Copy
39+
</button>
40+
</div>
41+
<div className={styles.buttons}>
42+
<button className="button button--primary button--sm share-button shadow--lw" onClick={onRequestClose}>
43+
OK
44+
</button>
45+
</div>
46+
</Modal>
47+
);
48+
};
49+
50+
export default CopyDialog;

docs/src/components/TransformerTester.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import MarkmapView from "./markmap/MarkmapView";
1111
import transformerToMarkmap from "./transformerToMarkmap";
1212
import Modal from "./Modal";
1313
import { extractValue, SHARE_QS, shareLink } from "@site/src/utils/shareLink";
14+
import CopyDialog from "@site/src/components/CopyDialog";
1415

1516
const DEFAULT_INPUT_VALUE = `{
1617
"first_name": "John",
@@ -60,6 +61,7 @@ const TransformerTester = () => {
6061
const [outputError, setOutputError] = useState<string | null>(null);
6162
const [loading, setLoading] = useState(false);
6263
const [modalOpen, setModalOpen] = useState(false);
64+
const [shareValue, setShareValue] = useState("");
6365

6466
const location = useLocation();
6567
const history = useHistory();
@@ -129,7 +131,7 @@ const TransformerTester = () => {
129131

130132
const handleShare = async () => {
131133
const url = await shareLink(parsedInput, parsedTransformer);
132-
prompt("Copy this value and share it", url);
134+
setShareValue(url);
133135
};
134136

135137
const handleVisualize = () => {
@@ -238,6 +240,9 @@ const TransformerTester = () => {
238240
/>
239241
</Modal>
240242
)}
243+
<CopyDialog open={!!shareValue} title="Share" onRequestClose={() => setShareValue("")} value={shareValue}>
244+
Copy this value and share it
245+
</CopyDialog>
241246
</>
242247
);
243248
};

docs/src/utils/Base64.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const BASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2+
// Base64 (The Base64 Alphabet)
3+
const RFC2045 = BASE + "+/";
4+
const lookup2045 = typeof Uint8Array === "undefined" ? [] : new Uint8Array(256);
5+
for (let i = 0; i < RFC2045.length; i++) {
6+
lookup2045[RFC2045.charCodeAt(i)] = i;
7+
}
8+
// Base64 URL (URL and Filename safe Base64 Alphabet)
9+
const RFC4648 = BASE + "-_";
10+
const lookup4648 = typeof Uint8Array === "undefined" ? [] : new Uint8Array(256);
11+
for (let i = 0; i < RFC4648.length; i++) {
12+
lookup4648[RFC4648.charCodeAt(i)] = i;
13+
}
14+
15+
export const encode = (arraybuffer: ArrayBuffer, url?: boolean): string => {
16+
const chars = url ? RFC4648 : RFC2045;
17+
18+
let bytes = new Uint8Array(arraybuffer),
19+
i,
20+
len = bytes.length,
21+
base64 = "";
22+
23+
for (i = 0; i < len; i += 3) {
24+
base64 += chars[bytes[i] >> 2];
25+
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
26+
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
27+
base64 += chars[bytes[i + 2] & 63];
28+
}
29+
30+
if (len % 3 === 2) {
31+
base64 = base64.substring(0, base64.length - 1) + "=";
32+
} else if (len % 3 === 1) {
33+
base64 = base64.substring(0, base64.length - 2) + "==";
34+
}
35+
36+
console.log(base64.length);
37+
return base64;
38+
};
39+
40+
export const decode = (base64: string, url?: boolean): ArrayBuffer => {
41+
const lookup = url ? lookup4648 : lookup2045;
42+
let bufferLength = base64.length * 0.75,
43+
encoded1: number,
44+
encoded2: number,
45+
encoded3: number,
46+
encoded4: number;
47+
48+
if (base64[base64.length - 1] === "=") {
49+
bufferLength--;
50+
if (base64[base64.length - 2] === "=") {
51+
bufferLength--;
52+
}
53+
}
54+
55+
const arraybuffer = new ArrayBuffer(bufferLength),
56+
bytes = new Uint8Array(arraybuffer);
57+
58+
for (let i = 0, p = 0, len = base64.length; i < len; i += 4) {
59+
encoded1 = lookup[base64.charCodeAt(i)];
60+
encoded2 = lookup[base64.charCodeAt(i + 1)];
61+
encoded3 = lookup[base64.charCodeAt(i + 2)];
62+
encoded4 = lookup[base64.charCodeAt(i + 3)];
63+
64+
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
65+
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
66+
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
67+
}
68+
69+
return arraybuffer;
70+
};

docs/src/utils/JSONGzip.ts

Lines changed: 11 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,24 @@
1-
const Base64Url = {
2-
from(b64: string) {
3-
return b64.replace(/=+/g, "").replace(/\+/g, "-").replace(/\//g, "_");
4-
},
5-
toBase64(b64Url: string) {
6-
return b64Url.replace(/-/g, "+").replace(/_/g, "/");
7-
},
8-
};
9-
10-
const blobToBase64 = (blob: Blob): Promise<string> => {
11-
const reader = new FileReader();
12-
reader.readAsDataURL(blob);
13-
return new Promise(resolve => {
14-
reader.onloadend = () => {
15-
const dataUrl = reader.result as string;
16-
resolve(dataUrl.substring(dataUrl.indexOf(";") + 1).replace(/^base64,/, ""));
17-
};
18-
});
19-
};
20-
21-
const base64ToUint8Array = (base64: string) => {
22-
base64 = base64.replace(/[\s\t\n]/g, "");
23-
if (base64.length % 4 !== 0) {
24-
base64 = base64 + "=".repeat(4 - (base64.length % 4));
25-
}
26-
const bytesString = atob(base64);
27-
const length = bytesString.length;
28-
const bytes = new Uint8Array(length);
29-
for (let i = 0; i < length; i++) {
30-
bytes[i] = bytesString.charCodeAt(i);
31-
}
32-
return bytes;
33-
};
1+
import { decode, encode } from "./Base64";
342

353
class JSONGzip {
364
public static async compress(
375
value: string,
386
format: "base64" | "base64url" = "base64",
397
valueType: "string" | "json" = "json",
408
) {
41-
const stream = new Blob([valueType === "string" ? value : JSON.stringify(value)], {
42-
type: "application/json",
43-
}).stream();
44-
const compressedStream = stream.pipeThrough(new CompressionStream("gzip"));
45-
const response = new Response(compressedStream);
46-
const blob = await response.blob();
47-
const base64 = await blobToBase64(blob);
48-
// const buffer = await response.blob().then(blob => blob.arrayBuffer());
49-
// const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
50-
if (format === "base64url") {
51-
return Base64Url.from(base64);
52-
}
53-
return base64;
9+
const input = valueType === "string" ? value : JSON.stringify(value);
10+
const compressionStream = new Blob([input], { type: "application/json" })
11+
.stream()
12+
.pipeThrough(new CompressionStream("gzip"));
13+
return new Response(compressionStream).arrayBuffer().then(buffer => encode(buffer, format === "base64url"));
5414
}
5515

5616
public static async decompress(compressed: string, format: "base64" | "base64url" = "base64") {
57-
const base64Input = format === "base64url" ? Base64Url.toBase64(compressed) : compressed;
58-
const buffer = base64ToUint8Array(base64Input);
59-
const stream = new Blob([buffer], { type: "text/plain" }).stream();
60-
const decompressedStream = stream.pipeThrough(new DecompressionStream("gzip"));
61-
const response = new Response(decompressedStream);
62-
return response
17+
const buffer = decode(compressed, format === "base64url");
18+
const decompressionStream = new Blob([buffer], { type: "application/json" })
19+
.stream()
20+
.pipeThrough(new DecompressionStream("gzip"));
21+
return new Response(decompressionStream)
6322
.blob()
6423
.then(blob => blob.text())
6524
.then(text => JSON.parse(text));

docs/src/utils/shareLink.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,25 @@ export const GZIP_MARKER = "gzip,";
66
export type Bundle = {
77
i: any;
88
d: any;
9-
}
9+
};
1010

11-
const BASE_URL = "/json-transform/"
11+
const BASE_URL = "/json-transform/playground";
1212

1313
export const shareLink = async (input: any, definition: any) => {
14-
const bundleString = JSON.stringify({i: input, d: definition});
15-
const compressed = await JSONGzip.compress(bundleString, "base64url", "string");
16-
const encodedInput = encodeURIComponent(bundleString);
17-
const value = encodedInput.length > compressed.length + GZIP_MARKER.length
18-
? GZIP_MARKER + compressed // compressed is better
19-
: encodedInput;
14+
const jsonValue = JSON.stringify({ i: input, d: definition });
15+
const compressed = await JSONGzip.compress(jsonValue, "base64url", "string");
16+
const encodedCompressedValue = encodeURIComponent(GZIP_MARKER + compressed);
17+
const encodedJSONValue = encodeURIComponent(jsonValue);
18+
const encodedValue =
19+
encodedJSONValue.length > encodedCompressedValue.length
20+
? encodedCompressedValue // compressed is better
21+
: encodedJSONValue;
2022

21-
return window.location.origin + `${BASE_URL}playground#?${SHARE_QS}=` + value;
22-
}
23+
return (typeof window !== "undefined" ? window.location.origin : "") + `${BASE_URL}#?${SHARE_QS}=` + encodedValue;
24+
};
2325

2426
export const extractValue = (sharedValue: string) => {
2527
return sharedValue.startsWith(GZIP_MARKER)
26-
? JSONGzip.decompress(sharedValue.substring(GZIP_MARKER.length), "base64url") as Promise<Bundle>
27-
: Promise.resolve(JSON.parse(sharedValue) as Bundle)
28-
}
28+
? (JSONGzip.decompress(sharedValue.substring(GZIP_MARKER.length), "base64url") as Promise<Bundle>)
29+
: Promise.resolve(JSON.parse(sharedValue) as Bundle);
30+
};

0 commit comments

Comments
 (0)