First pass at save/upload

This commit is contained in:
Chandler Abraham
2025-05-31 14:31:13 -07:00
parent ba4db567cf
commit b11de530f4
5 changed files with 90 additions and 4 deletions
+30
View File
@@ -76,3 +76,33 @@
outline-offset: -2px;
background-color: rgba(76, 175, 80, 0.05) !important;
}
/* Download button styles */
.download-button {
position: absolute;
top: 8px;
right: 8px;
z-index: 100;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
padding: 6px;
cursor: pointer;
color: rgba(255, 255, 255, 0.6);
transition: all 0.2s ease;
pointer-events: auto;
}
.download-button:hover {
background: rgba(0, 0, 0, 0.5);
color: rgba(255, 255, 255, 0.9);
border-color: rgba(255, 255, 255, 0.2);
}
.download-button:active {
transform: scale(0.95);
}
.download-button svg {
display: block;
}
+7 -3
View File
@@ -1,14 +1,16 @@
import DownloadButton from './DownloadButton';
// type Props = {
// containerRef: React.MutableRefObject<HTMLElement | null>,
// editorRef: React.MutableRefObject<HTMLElement | null>,
// init: () => void
// }
export function Code(Props) {
const { editorRef, containerRef, init } = Props;
const { editorRef, containerRef, init, context } = Props;
return (
<section
className={'text-gray-100 cursor-text pb-0 overflow-auto grow'}
className={'text-gray-100 cursor-text pb-0 overflow-auto grow relative'}
id="code"
ref={(el) => {
containerRef.current = el;
@@ -16,6 +18,8 @@ export function Code(Props) {
init();
}
}}
></section>
>
{context && <DownloadButton context={context} />}
</section>
);
}
@@ -0,0 +1,51 @@
import { useCallback } from 'react';
export default function DownloadButton({ context }) {
const handleDownload = useCallback(() => {
// Get the current code from the editor
const code = context.code;
// Create a blob with the code
const blob = new Blob([code], { type: 'text/javascript' });
// Create a temporary URL for the blob
const url = window.URL.createObjectURL(blob);
// Create a temporary anchor element and trigger download
const a = document.createElement('a');
a.href = url;
a.download = `strudel-pattern-${new Date().toISOString().slice(0, 10)}.js`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Clean up the URL
window.URL.revokeObjectURL(url);
}, [context.code]);
return (
<button
onClick={handleDownload}
className="download-button"
title="Download pattern as .js file"
aria-label="Download pattern"
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 12L3 7L4.4 5.6L7 8.2V0H9V8.2L11.6 5.6L13 7L8 12Z"
fill="currentColor"
/>
<path
d="M14 14V10H16V16H0V10H2V14H14Z"
fill="currentColor"
/>
</svg>
</button>
);
}
+1
View File
@@ -1,5 +1,6 @@
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
import ArrowDownTrayIcon from '@heroicons/react/20/solid/ArrowDownTrayIcon';
import cx from '@src/cx.mjs';
import { useSettings, setIsZen } from '../../settings.mjs';
import '../Repl.css';
+1 -1
View File
@@ -20,7 +20,7 @@ export default function ReplEditor(Props) {
<Loader active={pending} />
<Header context={context} />
<div className="grow flex relative overflow-hidden">
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
<Code containerRef={containerRef} editorRef={editorRef} init={init} context={context} />
{!isZen && panelPosition === 'right' && <VerticalPanel context={context} />}
</div>
<UserFacingErrorMessage error={error} />