diff --git a/website/src/repl/components/button/action-button.jsx b/website/src/repl/components/button/action-button.jsx index d589b7be..de674b69 100644 --- a/website/src/repl/components/button/action-button.jsx +++ b/website/src/repl/components/button/action-button.jsx @@ -8,3 +8,34 @@ export function ActionButton({ children, label, labelIsHidden, className, ...but ); } + +export function SpecialActionButton(props) { + const { className, ...buttonProps } = props; + + return ( + + ); +} + +export function ActionInput({ label, className, ...props }) { + return ( + + ); +} + +export function SpecialActionInput({ className, ...props }) { + return ( + {props.label}} + /> + ); +} diff --git a/website/src/repl/components/panel/ImportPrebakeScriptButton.jsx b/website/src/repl/components/panel/ImportPrebakeScriptButton.jsx new file mode 100644 index 00000000..c997f6b6 --- /dev/null +++ b/website/src/repl/components/panel/ImportPrebakeScriptButton.jsx @@ -0,0 +1,29 @@ +import { errorLogger } from '@strudel/core'; +import { useSettings, storePrebakeScript } from '../../../settings.mjs'; +import { SpecialActionInput } from '../button/action-button'; + +async function importScript(script) { + const reader = new FileReader(); + reader.readAsText(script); + + reader.onload = () => { + const text = reader.result; + storePrebakeScript(text); + }; + + reader.onerror = () => { + errorLogger(new Error('failed to import prebake script'), 'importScript'); + }; +} +export function ImportPrebakeScriptButton() { + const settings = useSettings(); + + return ( + importScript(e.target.files[0])} + /> + ); +} diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index 85991f55..f46fa661 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -7,6 +7,8 @@ import { AudioDeviceSelector } from './AudioDeviceSelector.jsx'; import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx'; import { confirmDialog } from '../../util.mjs'; import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio'; +import { ActionButton, SpecialActionButton } from '../button/action-button.jsx'; +import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx'; function Checkbox({ label, value, onChange, disabled = false }) { return ( @@ -113,6 +115,7 @@ export function SettingsTab({ started }) { isTabIndentationEnabled, isMultiCursorEnabled, patternAutoStart, + includePrebakeScriptInShare, } = useSettings(); const shouldAlwaysSync = isUdels(); const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; @@ -204,6 +207,15 @@ export function SettingsTab({ started }) { /> + + + settingsMap.setKey('includePrebakeScriptInShare', cbEvent.target.checked)} + value={includePrebakeScriptInShare} + /> + + Try clicking the logo in the top left! - + ); diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 8a6ccc7e..53a2a600 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -3,6 +3,8 @@ import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@st import { registerSamplesFromDB } from './idbutils.mjs'; import './piano.mjs'; import './files.mjs'; +import { settingsMap } from '@src/settings.mjs'; +import { evaluate } from '@strudel/transpiler'; const { BASE_URL } = import.meta.env; const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index ac30fe34..8dcb6b75 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core'; import { getDrawContext } from '@strudel/draw'; -import { transpiler } from '@strudel/transpiler'; +import { evaluate, transpiler } from '@strudel/transpiler'; import { getAudioContextCurrentTime, webaudioOutput, @@ -63,11 +63,10 @@ async function getModule(name) { const initialCode = `// LOADING`; export function useReplContext() { - const { isSyncEnabled, audioEngineTarget } = useSettings(); + const { isSyncEnabled, audioEngineTarget, prebakeScript, includePrebakeScriptInShare } = useSettings(); const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc; const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput; const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds; - const init = useCallback(() => { const drawTime = [-2, 2]; const drawContext = getDrawContext(); @@ -84,7 +83,12 @@ export function useReplContext() { pattern: silence, drawTime, drawContext, - prebake: async () => Promise.all([modulesLoading, presets]), + prebake: async () => + Promise.all([modulesLoading, presets]).then(() => { + if (prebakeScript?.length) { + return evaluate(prebakeScript ?? ''); + } + }), onUpdateState: (state) => { setReplState({ ...state }); }, @@ -214,7 +218,13 @@ export function useReplContext() { editorRef.current.repl.evaluate(code); }; - const handleShare = async () => shareCode(replState.code); + const handleShare = async () => { + let code = replState.code; + if (includePrebakeScriptInShare) { + code = prebakeScript + '\n' + code; + } + shareCode(code); + }; const context = { started, pending, diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index ac27158f..4a7cb26a 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -1,11 +1,10 @@ -import { evalScope, hash2code, logger } from '@strudel/core'; +import { code2hash, evalScope, hash2code, logger } from '@strudel/core'; import { settingPatterns } from '../settings.mjs'; import { setVersionDefaults } from '@strudel/webaudio'; import { getMetadata } from '../metadata_parser'; import { isTauri } from '../tauri.mjs'; import './Repl.css'; import { createClient } from '@supabase/supabase-js'; -import { nanoid } from 'nanoid'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs'; @@ -109,9 +108,8 @@ export function confirmDialog(msg) { }); } -let lastShared; - //RIP due to SPAM +// let lastShared; // export async function shareCode(codeToShare) { // // const codeToShare = activeCode || code; // if (lastShared === codeToShare) { @@ -146,9 +144,10 @@ let lastShared; // }); // } -export async function shareCode() { +export async function shareCode(codeToShare) { try { - const shareUrl = window.location.href; + const hash = '#' + code2hash(codeToShare); + const shareUrl = window.location.origin + window.location.pathname + hash; if (isTauri()) { await writeText(shareUrl); } else { diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 7c62b0de..8ec7d455 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -45,11 +45,13 @@ export const defaultSettings = { isPanelOpen: true, togglePanelTrigger: 'click', //click | hover userPatterns: '{}', + prebakeScript: '', audioEngineTarget: audioEngineTargets.webaudio, isButtonRowHidden: false, isCSSAnimationDisabled: false, maxPolyphony: 128, multiChannelOrbits: false, + includePrebakeScriptInShare: true, }; let search = null; @@ -96,6 +98,7 @@ export function useSettings() { isPanelOpen: parseBoolean(state.isPanelOpen), userPatterns: userPatterns, multiChannelOrbits: parseBoolean(state.multiChannelOrbits), + includePrebakeScriptInShare: parseBoolean(state.includePrebakeScriptInShare), patternAutoStart: isUdels() ? false : state.patternAutoStart === undefined @@ -108,6 +111,8 @@ export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab); export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool); export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool); +export const storePrebakeScript = (script) => settingsMap.setKey('prebakeScript', script); + export const setIsZen = (active) => settingsMap.setKey('isZen', !!active); const patternSetting = (key) =>