mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-24 03:58:53 +00:00
restored
This commit is contained in:
parent
e0c4997f1e
commit
5ca9afa9e8
33
website/src/components/Udels/UdelFrame.jsx
Normal file
33
website/src/components/Udels/UdelFrame.jsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
export function UdelFrame({ onEvaluate, hash, instance }) {
|
||||||
|
const ref = useRef();
|
||||||
|
|
||||||
|
window.addEventListener('message', (message) => {
|
||||||
|
const childWindow = ref?.current?.contentWindow;
|
||||||
|
if (message == null || message.source !== childWindow) {
|
||||||
|
return; // Skip message in this event listener
|
||||||
|
}
|
||||||
|
onEvaluate(message.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = new URL(window.location.origin);
|
||||||
|
url.hash = hash;
|
||||||
|
url.searchParams.append('instance', instance);
|
||||||
|
const source = url.toString();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
ref={ref}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexGrow: 1,
|
||||||
|
minWidth: '50%',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
border: '0px',
|
||||||
|
}}
|
||||||
|
title="strudel embed"
|
||||||
|
src={source}
|
||||||
|
></iframe>
|
||||||
|
);
|
||||||
|
}
|
||||||
115
website/src/components/Udels/Udels.jsx
Normal file
115
website/src/components/Udels/Udels.jsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { code2hash } from '@strudel/core';
|
||||||
|
|
||||||
|
import { UdelFrame } from './UdelFrame';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
function NumberInput({ value, onChange, label = '', min, max }) {
|
||||||
|
const [localState, setLocalState] = useState(value);
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label>
|
||||||
|
{label}
|
||||||
|
<input
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
className="p-2 bg-background rounded-md text-foreground"
|
||||||
|
type={'number'}
|
||||||
|
value={(isFocused ? localState : value) ?? ''}
|
||||||
|
onFocus={() => {
|
||||||
|
setLocalState(value);
|
||||||
|
setIsFocused(true);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
onChange(Math.max(localState, min));
|
||||||
|
setIsFocused(false);
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
let val = e.target.value;
|
||||||
|
val = val.length ? Math.min(parseFloat(e.target.value), max) : null;
|
||||||
|
setLocalState(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const defaultHash = 'c3RhY2soCiAgCik%3D';
|
||||||
|
|
||||||
|
const getHashesFromUrl = () => {
|
||||||
|
return window.location.hash?.slice(1).split(',');
|
||||||
|
};
|
||||||
|
const updateURLHashes = (hashes) => {
|
||||||
|
const newHash = '#' + hashes.join(',');
|
||||||
|
window.location.hash = newHash;
|
||||||
|
};
|
||||||
|
export function Udels() {
|
||||||
|
const hashes = getHashesFromUrl();
|
||||||
|
|
||||||
|
const [numWindows, setNumWindows] = useState(hashes?.length ?? 1);
|
||||||
|
const numWindowsOnChange = (num) => {
|
||||||
|
setNumWindows(num);
|
||||||
|
const hashes = getHashesFromUrl();
|
||||||
|
const newHashes = [];
|
||||||
|
for (let i = 0; i < num; i++) {
|
||||||
|
newHashes[i] = hashes[i] ?? defaultHash;
|
||||||
|
}
|
||||||
|
updateURLHashes(newHashes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEvaluate = (key, code) => {
|
||||||
|
const hashes = getHashesFromUrl();
|
||||||
|
hashes[key] = code2hash(code);
|
||||||
|
updateURLHashes(hashes);
|
||||||
|
};
|
||||||
|
// useEffect(() => {
|
||||||
|
// prebake();
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'teal',
|
||||||
|
margin: 0,
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1,
|
||||||
|
height: '100vh',
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 40,
|
||||||
|
width: '100',
|
||||||
|
position: 'absolute',
|
||||||
|
color: 'white',
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NumberInput min={1} max={8} value={numWindows} onChange={numWindowsOnChange} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hashes.map((hash, key) => {
|
||||||
|
return (
|
||||||
|
<UdelFrame
|
||||||
|
instance={key}
|
||||||
|
onEvaluate={(code) => {
|
||||||
|
onEvaluate(key, code);
|
||||||
|
}}
|
||||||
|
hash={hash}
|
||||||
|
key={key}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{/* <Panel /> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -30,7 +30,7 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
|
|||||||
</title>
|
</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="h-app-height text-gray-50 bg-background">
|
<body class="h-app-height m-0 text-gray-50 bg-background">
|
||||||
<div class="w-full h-full space-y-4 flex flex-col">
|
<div class="w-full h-full space-y-4 flex flex-col">
|
||||||
<header class="max-w-full fixed top-0 w-full z-[100]">
|
<header class="max-w-full fixed top-0 w-full z-[100]">
|
||||||
<Header currentPage={currentPage} />
|
<Header currentPage={currentPage} />
|
||||||
|
|||||||
@ -3,12 +3,12 @@ import HeadCommon from '../components/HeadCommon.astro';
|
|||||||
import { Repl } from '../repl/Repl';
|
import { Repl } from '../repl/Repl';
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en" class="dark">
|
<html lang="en" class="m-0 dark">
|
||||||
<head>
|
<head>
|
||||||
<HeadCommon />
|
<HeadCommon />
|
||||||
<title>Strudel REPL</title>
|
<title>Strudel REPL</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="h-app-height bg-background">
|
<body class="h-app-height bg-background m-0">
|
||||||
<Repl client:only="react" />
|
<Repl client:only="react" />
|
||||||
<a rel="me" href="https://social.toplap.org/@strudel" target="_blank" class="hidden">mastodon</a>
|
<a rel="me" href="https://social.toplap.org/@strudel" target="_blank" class="hidden">mastodon</a>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
12
website/src/pages/udels/index.astro
Normal file
12
website/src/pages/udels/index.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
import { Udels } from '../../components/Udels/Udels.jsx';
|
||||||
|
|
||||||
|
|
||||||
|
const { BASE_URL } = import.meta.env;
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
<body class="m-0">
|
||||||
|
<Udels client:only="react" />
|
||||||
|
</body>
|
||||||
@ -34,7 +34,7 @@ import Loader from './Loader';
|
|||||||
import { Panel } from './panel/Panel';
|
import { Panel } from './panel/Panel';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { prebake } from './prebake.mjs';
|
import { prebake } from './prebake.mjs';
|
||||||
import { getRandomTune, initCode, loadModules, shareCode, ReplContext } from './util.mjs';
|
import { getRandomTune, initCode, loadModules, shareCode, ReplContext, isUdels } from './util.mjs';
|
||||||
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||||
import './Repl.css';
|
import './Repl.css';
|
||||||
import { setInterval, clearInterval } from 'worker-timers';
|
import { setInterval, clearInterval } from 'worker-timers';
|
||||||
@ -221,6 +221,7 @@ export function Repl({ embedded = false }) {
|
|||||||
handleEvaluate,
|
handleEvaluate,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showPanel = !isEmbedded || isUdels();
|
||||||
return (
|
return (
|
||||||
<ReplContext.Provider value={context}>
|
<ReplContext.Provider value={context}>
|
||||||
<div className={cx('h-full flex flex-col relative')}>
|
<div className={cx('h-full flex flex-col relative')}>
|
||||||
@ -246,12 +247,12 @@ export function Repl({ embedded = false }) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
></section>
|
></section>
|
||||||
{panelPosition === 'right' && !isEmbedded && <Panel context={context} />}
|
{panelPosition === 'right' && showPanel && <Panel context={context} />}
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="text-red-500 p-4 bg-lineHighlight animate-pulse">{error.message || 'Unknown Error :-/'}</div>
|
<div className="text-red-500 p-4 bg-lineHighlight animate-pulse">{error.message || 'Unknown Error :-/'}</div>
|
||||||
)}
|
)}
|
||||||
{panelPosition === 'bottom' && !isEmbedded && <Panel context={context} />}
|
{panelPosition === 'bottom' && showPanel && <Panel context={context} />}
|
||||||
</div>
|
</div>
|
||||||
</ReplContext.Provider>
|
</ReplContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { getMetadata } from '../../metadata_parser';
|
import { getMetadata } from '../../metadata_parser';
|
||||||
import { useExamplePatterns } from '../useExamplePatterns';
|
import { useExamplePatterns } from '../useExamplePatterns';
|
||||||
import { parseJSON } from '../util.mjs';
|
import { parseJSON, isUdels } from '../util.mjs';
|
||||||
import { ButtonGroup } from './Forms.jsx';
|
import { ButtonGroup } from './Forms.jsx';
|
||||||
import { settingsMap, useSettings } from '../../settings.mjs';
|
import { settingsMap, useSettings } from '../../settings.mjs';
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ export function PatternsTab({ context }) {
|
|||||||
};
|
};
|
||||||
const viewingPatternID = viewingPatternData?.id;
|
const viewingPatternID = viewingPatternData?.id;
|
||||||
|
|
||||||
const autoResetPatternOnChange = !window.parent?.location.pathname.includes('oodles');
|
const autoResetPatternOnChange = !isUdels();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-4 w-full dark:text-white text-stone-900 space-y-2 pb-4 flex flex-col overflow-hidden max-h-full">
|
<div className="px-4 w-full dark:text-white text-stone-900 space-y-2 pb-4 flex flex-col overflow-hidden max-h-full">
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { defaultSettings, settingsMap, useSettings } from '../../settings.mjs';
|
import { defaultSettings, settingsMap, useSettings } from '../../settings.mjs';
|
||||||
import { themes } from '@strudel/codemirror';
|
import { themes } from '@strudel/codemirror';
|
||||||
|
import { isUdels } from '../util.mjs';
|
||||||
import { ButtonGroup } from './Forms.jsx';
|
import { ButtonGroup } from './Forms.jsx';
|
||||||
import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
|
import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
|
||||||
|
|
||||||
function Checkbox({ label, value, onChange }) {
|
function Checkbox({ label, value, onChange, disabled = false }) {
|
||||||
return (
|
return (
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" checked={value} onChange={onChange} />
|
<input disabled={disabled} type="checkbox" checked={value} onChange={onChange} />
|
||||||
{' ' + label}
|
{' ' + label}
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
@ -96,7 +97,7 @@ export function SettingsTab({ started }) {
|
|||||||
panelPosition,
|
panelPosition,
|
||||||
audioDeviceName,
|
audioDeviceName,
|
||||||
} = useSettings();
|
} = useSettings();
|
||||||
|
const shouldAlwaysSync = isUdels();
|
||||||
return (
|
return (
|
||||||
<div className="text-foreground p-4 space-y-4">
|
<div className="text-foreground p-4 space-y-4">
|
||||||
{AudioContext.prototype.setSinkId != null && (
|
{AudioContext.prototype.setSinkId != null && (
|
||||||
@ -197,6 +198,7 @@ export function SettingsTab({ started }) {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
disabled={shouldAlwaysSync}
|
||||||
value={isSyncEnabled}
|
value={isSyncEnabled}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -132,6 +132,10 @@ export async function shareCode(codeToShare) {
|
|||||||
|
|
||||||
export const ReplContext = createContext(null);
|
export const ReplContext = createContext(null);
|
||||||
|
|
||||||
|
export const isUdels = () => {
|
||||||
|
return window.parent?.location.pathname.includes('udels');
|
||||||
|
};
|
||||||
|
|
||||||
export const getAudioDevices = async () => {
|
export const getAudioDevices = async () => {
|
||||||
await navigator.mediaDevices.getUserMedia({ audio: true });
|
await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
|
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { persistentMap } from '@nanostores/persistent';
|
import { persistentMap } from '@nanostores/persistent';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { register } from '@strudel/core';
|
import { register } from '@strudel/core';
|
||||||
|
import { isUdels } from './repl/util.mjs';
|
||||||
|
|
||||||
export const defaultAudioDeviceName = 'System Standard';
|
export const defaultAudioDeviceName = 'System Standard';
|
||||||
|
|
||||||
@ -28,8 +29,12 @@ export const defaultSettings = {
|
|||||||
userPatterns: '{}',
|
userPatterns: '{}',
|
||||||
audioDeviceName: defaultAudioDeviceName,
|
audioDeviceName: defaultAudioDeviceName,
|
||||||
};
|
};
|
||||||
|
const search = new URLSearchParams(window.location.search);
|
||||||
|
// if running multiple instance in one window, it will use the settings for that instance. else default to normal
|
||||||
|
const instance = parseInt(search.get('instance') ?? '0');
|
||||||
|
const settings_key = `strudel-settings${instance > 0 ? instance : ''}`;
|
||||||
|
|
||||||
export const settingsMap = persistentMap('strudel-settings', defaultSettings);
|
export const settingsMap = persistentMap(settings_key, defaultSettings);
|
||||||
|
|
||||||
const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
|
const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
|
||||||
|
|
||||||
@ -54,7 +59,7 @@ export function useSettings() {
|
|||||||
isTooltipEnabled: parseBoolean(state.isTooltipEnabled),
|
isTooltipEnabled: parseBoolean(state.isTooltipEnabled),
|
||||||
isLineWrappingEnabled: parseBoolean(state.isLineWrappingEnabled),
|
isLineWrappingEnabled: parseBoolean(state.isLineWrappingEnabled),
|
||||||
isFlashEnabled: parseBoolean(state.isFlashEnabled),
|
isFlashEnabled: parseBoolean(state.isFlashEnabled),
|
||||||
isSyncEnabled: parseBoolean(state.isSyncEnabled),
|
isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled),
|
||||||
fontSize: Number(state.fontSize),
|
fontSize: Number(state.fontSize),
|
||||||
panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
|
panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
|
||||||
userPatterns: userPatterns,
|
userPatterns: userPatterns,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user