From 5bddb6f48d969427d977ad807f3e7c3f0f482703 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sun, 30 Nov 2025 10:09:06 +0300 Subject: [PATCH] Move export UI to the panel --- website/src/repl/components/ExportModal.jsx | 225 ------------------ website/src/repl/components/Header.jsx | 15 +- .../src/repl/components/panel/ExportTab.jsx | 197 +++++++++++++++ website/src/repl/components/panel/Panel.jsx | 4 + 4 files changed, 203 insertions(+), 238 deletions(-) delete mode 100644 website/src/repl/components/ExportModal.jsx create mode 100644 website/src/repl/components/panel/ExportTab.jsx diff --git a/website/src/repl/components/ExportModal.jsx b/website/src/repl/components/ExportModal.jsx deleted file mode 100644 index bac8c6b14..000000000 --- a/website/src/repl/components/ExportModal.jsx +++ /dev/null @@ -1,225 +0,0 @@ -import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; -import cx from '@src/cx.mjs'; -import NumberInput from './NumberInput'; -import { useEffect, useState } from 'react'; -import { Textbox } from './textbox/Textbox'; -import { getAudioContext } from '@strudel/webaudio'; -import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon'; - -function Checkbox({ label, value, onChange, disabled = false }) { - return ( - - ); -} - -function FormItem({ label, children, disabled }) { - return ( -
- - {children} -
- ); -} - -export default function ExportModal(Props) { - const { started, isEmbedded, handleExport } = Props; - - const [downloadName, setDownloadName] = useState(''); // TODO: make a form? - const [startCycle, setStartCycle] = useState(0); - const [endCycle, setEndCycle] = useState(1); - const [sampleRate, setSampleRate] = useState(48000); - const [multiChannelOrbits, setMultiChannelOrbits] = useState(true); - const [maxPolyphony, setMaxPolyphony] = useState(1024); - const [exporting, setExporting] = useState(false); - const [progress, setProgress] = useState(0); - const [length, setLength] = useState(1); - - const refreshProgress = () => { - const audioContext = getAudioContext(); - if (audioContext instanceof OfflineAudioContext) { - setProgress(audioContext.currentTime); - setLength(audioContext.length / sampleRate); - setTimeout(refreshProgress, 100); - } - }; - - return ( - <> - - - -
- - { - setDownloadName(e.target.value); - }} - onChange={(v) => { - setDownloadName(v); - }} - disabled={exporting} - placeholder="Leave empty to use current date" - className={cx('placeholder:opacity-50', exporting && 'opacity-50 border-opacity-50')} - value={downloadName ?? ''} - /> - -
- - { - let v = parseInt(e.target.value); - v = isNaN(v) ? 0 : Math.max(0, v); - setStartCycle(v); - }} - onChange={(v) => { - v = parseInt(v); - setStartCycle(v); - }} - type="number" - placeholder="" - disabled={exporting} - className={cx(exporting && 'opacity-50 border-opacity-50')} - value={startCycle ?? ''} - /> - - - { - let v = parseInt(e.target.value); - v = isNaN(v) ? Math.max(startCycle + 1, parseInt(v)) : v; - setEndCycle(v); - }} - onChange={(v) => { - v = parseInt(v); - setEndCycle(v); - }} - type="number" - placeholder="" - disabled={exporting} - className={cx(exporting && 'opacity-50 border-opacity-50')} - value={endCycle ?? ''} - /> - -
-
- - { - let v = parseInt(e.target.value); - v = isNaN(v) ? 1 : Math.max(1, v); - setSampleRate(v); - }} - onChange={(v) => { - v = parseInt(v); - setSampleRate(v); - }} - type="number" - placeholder="" - disabled={exporting} - className={cx(exporting && 'opacity-50 border-opacity-50')} - value={sampleRate ?? ''} - /> - - - { - let v = parseInt(e.target.value); - v = isNaN(v) ? Math.max(1, parseInt(v)) : v; - setMaxPolyphony(v); - }} - onChange={(v) => { - v = Math.max(1, parseInt(v)); - setMaxPolyphony(v); - }} - type="number" - placeholder="" - disabled={exporting} - className={cx(exporting && 'opacity-50 border-opacity-50')} - value={maxPolyphony ?? ''} - /> - -
-
- { - const val = cbEvent.target.checked; - setMultiChannelOrbits(val); - }} - disabled={exporting} - value={multiChannelOrbits} - /> -
- -
-
- - ); -} diff --git a/website/src/repl/components/Header.jsx b/website/src/repl/components/Header.jsx index dd9f684c1..ca4e1ecf9 100644 --- a/website/src/repl/components/Header.jsx +++ b/website/src/repl/components/Header.jsx @@ -3,23 +3,13 @@ import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon'; import cx from '@src/cx.mjs'; import { useSettings, setIsZen } from '../../settings.mjs'; import '../Repl.css'; -import ExportModal from './ExportModal'; const { BASE_URL } = import.meta.env; const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; export function Header({ context, embedded = false }) { - const { - started, - pending, - isDirty, - activeCode, - handleTogglePlay, - handleEvaluate, - handleShuffle, - handleShare, - handleExport, - } = context; + const { started, pending, isDirty, activeCode, handleTogglePlay, handleEvaluate, handleShuffle, handleShare } = + context; const isEmbedded = typeof window !== 'undefined' && (embedded || window.location !== window.parent.location); const { isZen, isButtonRowHidden, isCSSAnimationDisabled, fontFamily } = useSettings(); @@ -103,7 +93,6 @@ export function Header({ context, embedded = false }) { > {!isEmbedded && update} - {/* !isEmbedded && ( + + + + ); +} diff --git a/website/src/repl/components/panel/Panel.jsx b/website/src/repl/components/panel/Panel.jsx index b26c2edef..cfe90ae30 100644 --- a/website/src/repl/components/panel/Panel.jsx +++ b/website/src/repl/components/panel/Panel.jsx @@ -9,6 +9,7 @@ import { useLogger } from '../useLogger'; import { WelcomeTab } from './WelcomeTab'; import { PatternsTab } from './PatternsTab'; import { ChevronLeftIcon, XMarkIcon } from '@heroicons/react/16/solid'; +import ExportTab from './ExportTab'; const TAURI = typeof window !== 'undefined' && window.__TAURI__; @@ -80,6 +81,7 @@ const tabNames = { patterns: 'patterns', sounds: 'sounds', reference: 'reference', + export: 'export', console: 'console', settings: 'settings', }; @@ -126,6 +128,8 @@ function PanelContent({ context, tab }) { return ; case tabNames.reference: return ; + case tabNames.export: + return ; case tabNames.settings: return ; case tabNames.files: