-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Expand file tree
/
Copy pathDownloadButton.tsx
More file actions
115 lines (104 loc) · 3 KB
/
DownloadButton.tsx
File metadata and controls
115 lines (104 loc) · 3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import {useSyncExternalStore} from 'react';
import {useSandpack} from '@webcontainer/react';
import {IconDownload} from '../../Icon/IconDownload';
import {AppJSPath, StylesCSSPath, SUPPORTED_FILES} from './createFileMap';
export interface DownloadButtonProps {}
let supportsImportMap = false;
function subscribe(cb: () => void) {
// This shouldn't actually need to update, but this works around
// https://github.com/facebook/react/issues/26095
let timeout = setTimeout(() => {
supportsImportMap =
(HTMLScriptElement as any).supports &&
(HTMLScriptElement as any).supports('importmap');
cb();
}, 0);
return () => clearTimeout(timeout);
}
function useSupportsImportMap() {
function getCurrentValue() {
return supportsImportMap;
}
function getServerSnapshot() {
return false;
}
return useSyncExternalStore(subscribe, getCurrentValue, getServerSnapshot);
}
export function DownloadButton({
providedFiles,
}: {
providedFiles: Array<string>;
}) {
const {sandpack} = useSandpack();
const supported = useSupportsImportMap();
if (!supported) {
return null;
}
if (providedFiles.some((file) => !SUPPORTED_FILES.includes(file))) {
return null;
}
const downloadHTML = () => {
const css = sandpack.files[StylesCSSPath]?.code ?? '';
const code = sandpack.files[AppJSPath]?.code ?? '';
const blob = new Blob([
`<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
</body>
<!-- This setup is not suitable for production. -->
<!-- Only use it in development! -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script async src="https://ga.jspm.io/npm:es-module-shims@1.7.0/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react?dev",
"react-dom/client": "https://esm.sh/react-dom/client?dev"
}
}
</script>
<script type="text/babel" data-type="module">
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
${code.replace('export default ', 'let App = ')}
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);
</script>
<style>
${css}
</style>
</html>`,
]);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'sandbox.html';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
};
return (
<button
className="text-sm text-primary dark:text-primary-dark inline-flex items-center hover:text-link duration-100 ease-in transition mx-1"
onClick={downloadHTML}
title="Download Sandbox"
type="button">
<IconDownload className="inline me-1" /> Download
</button>
);
}