From 5be1004292832f5f0f51b053a1a9f0e5c86941c9 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 19 Aug 2023 00:19:32 +0200 Subject: [PATCH 01/72] encapsulate panel tabs --- website/src/repl/Footer.jsx | 381 +-------------------- website/src/repl/panel/ConsoleTab.jsx | 45 +++ website/src/repl/{ => panel}/FilesTab.jsx | 2 +- website/src/repl/panel/Forms.jsx | 24 ++ website/src/repl/{ => panel}/Reference.jsx | 2 +- website/src/repl/panel/SettingsTab.jsx | 173 ++++++++++ website/src/repl/panel/SoundsTab.jsx | 89 +++++ website/src/repl/panel/WelcomeTab.jsx | 49 +++ 8 files changed, 391 insertions(+), 374 deletions(-) create mode 100644 website/src/repl/panel/ConsoleTab.jsx rename website/src/repl/{ => panel}/FilesTab.jsx (97%) create mode 100644 website/src/repl/panel/Forms.jsx rename website/src/repl/{ => panel}/Reference.jsx (97%) create mode 100644 website/src/repl/panel/SettingsTab.jsx create mode 100644 website/src/repl/panel/SoundsTab.jsx create mode 100644 website/src/repl/panel/WelcomeTab.jsx diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index 77a3efbc..b27f11c0 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -1,15 +1,15 @@ import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon'; import { logger } from '@strudel.cycles/core'; -import { useEvent, cx } from '@strudel.cycles/react'; -// import { cx } from '@strudel.cycles/react'; +import { cx, useEvent } from '@strudel.cycles/react'; import { nanoid } from 'nanoid'; -import React, { useMemo, useCallback, useLayoutEffect, useRef, useState } from 'react'; -import { Reference } from './Reference'; -import { themes } from './themes.mjs'; -import { useSettings, settingsMap, setActiveFooter, defaultSettings } from '../settings.mjs'; -import { getAudioContext, soundMap } from '@strudel.cycles/webaudio'; -import { useStore } from '@nanostores/react'; -import { FilesTab } from './FilesTab'; +import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; +import { setActiveFooter, useSettings } from '../settings.mjs'; +import { ConsoleTab } from './panel/ConsoleTab'; +import { FilesTab } from './panel/FilesTab'; +import { Reference } from './panel/Reference'; +import { SettingsTab } from './panel/SettingsTab'; +import { SoundsTab } from './panel/SoundsTab'; +import { WelcomeTab } from './panel/WelcomeTab'; const TAURI = window.__TAURI__; @@ -114,366 +114,3 @@ export function Footer({ context }) { function useLogger(onTrigger) { useEvent(logger.key, onTrigger); } - -function linkify(inputText) { - var replacedText, replacePattern1, replacePattern2, replacePattern3; - - //URLs starting with http://, https://, or ftp:// - replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; - replacedText = inputText.replace(replacePattern1, '$1'); - - //URLs starting with "www." (without // before it, or it'd re-link the ones done above). - replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; - replacedText = replacedText.replace( - replacePattern2, - '$1$2', - ); - - //Change email addresses to mailto:: links. - replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; - replacedText = replacedText.replace(replacePattern3, '$1'); - - return replacedText; -} - -function WelcomeTab() { - return ( -
-

- πŸŒ€ welcome -

-

- You have found strudel, a new live coding platform to write dynamic music - pieces in the browser! It is free and open-source and made for beginners and experts alike. To get started: -
-
- 1. hit play - 2. change something -{' '} - 3. hit update -
- If you don't like what you hear, try shuffle! -

-

- To learn more about what this all means, check out the{' '} - - interactive tutorial - - . Also feel free to join the{' '} - - tidalcycles discord channel - {' '} - to ask any questions, give feedback or just say hello. -

-

about

-

- strudel is a JavaScript version of{' '} - - tidalcycles - - , which is a popular live coding language for music, written in Haskell. You can find the source code at{' '} - - github - - . Please consider to{' '} - - support this project - {' '} - to ensure ongoing development πŸ’– -

-
- ); -} - -function ConsoleTab({ log }) { - return ( -
-
{`β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—     
-β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘     
-β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘     
-β•šβ•β•β•β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘     
-β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
-β•šβ•β•β•β•β•β•β•   β•šβ•β•   β•šβ•β•  β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•`}
- {log.map((l, i) => { - const message = linkify(l.message); - return ( -
- - {l.count ? ` (${l.count})` : ''} -
- ); - })} -
- ); -} - -const getSamples = (samples) => - Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1; - -function SoundsTab() { - const sounds = useStore(soundMap); - const { soundsFilter } = useSettings(); - const soundEntries = useMemo(() => { - let filtered = Object.entries(sounds).filter(([key]) => !key.startsWith('_')); - if (!sounds) { - return []; - } - if (soundsFilter === 'user') { - return filtered.filter(([key, { data }]) => !data.prebake); - } - if (soundsFilter === 'drums') { - return filtered.filter(([_, { data }]) => data.type === 'sample' && data.tag === 'drum-machines'); - } - if (soundsFilter === 'samples') { - return filtered.filter(([_, { data }]) => data.type === 'sample' && data.tag !== 'drum-machines'); - } - if (soundsFilter === 'synths') { - return filtered.filter(([_, { data }]) => ['synth', 'soundfont'].includes(data.type)); - } - return filtered; - }, [sounds, soundsFilter]); - // holds mutable ref to current triggered sound - const trigRef = useRef(); - // stop current sound on mouseup - useEvent('mouseup', () => { - const t = trigRef.current; - trigRef.current = undefined; - t?.then((ref) => { - ref?.stop(getAudioContext().currentTime + 0.01); - }); - }); - return ( -
-
- settingsMap.setKey('soundsFilter', value)} - items={{ - samples: 'samples', - drums: 'drum-machines', - synths: 'Synths', - user: 'User', - }} - > -
-
- {soundEntries.map(([name, { data, onTrigger }]) => ( - { - const ctx = getAudioContext(); - const params = { - note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined, - s: name, - clip: 1, - release: 0.5, - }; - const time = ctx.currentTime + 0.05; - const onended = () => trigRef.current?.node?.disconnect(); - trigRef.current = Promise.resolve(onTrigger(time, params, onended)); - trigRef.current.then((ref) => { - ref?.node.connect(ctx.destination); - }); - }} - > - {' '} - {name} - {data?.type === 'sample' ? `(${getSamples(data.samples)})` : ''} - {data?.type === 'soundfont' ? `(${data.fonts.length})` : ''} - - ))} - {!soundEntries.length ? 'No custom sounds loaded in this pattern (yet).' : ''} -
-
- ); -} - -function Checkbox({ label, value, onChange }) { - return ( - - ); -} - -function ButtonGroup({ value, onChange, items }) { - return ( -
- {Object.entries(items).map(([key, label], i, arr) => ( - - ))} -
- ); -} - -function SelectInput({ value, options, onChange }) { - return ( - - ); -} - -function NumberSlider({ value, onChange, step = 1, ...rest }) { - return ( -
- onChange(Number(e.target.value))} - {...rest} - /> - onChange(Number(e.target.value))} - /> -
- ); -} - -function FormItem({ label, children }) { - return ( -
- - {children} -
- ); -} - -const themeOptions = Object.fromEntries(Object.keys(themes).map((k) => [k, k])); -const fontFamilyOptions = { - monospace: 'monospace', - BigBlueTerminal: 'BigBlueTerminal', - x3270: 'x3270', - PressStart: 'PressStart2P', - galactico: 'galactico', - 'we-come-in-peace': 'we-come-in-peace', - FiraCode: 'FiraCode', - 'FiraCode-SemiBold': 'FiraCode SemiBold', -}; - -function SettingsTab({ scheduler }) { - const { - theme, - keybindings, - isLineNumbersDisplayed, - isAutoCompletionEnabled, - isLineWrappingEnabled, - fontSize, - fontFamily, - panelPosition, - } = useSettings(); - - return ( -
- {/* -
- - -
-
*/} - - settingsMap.setKey('theme', theme)} /> - -
- - settingsMap.setKey('fontFamily', fontFamily)} - /> - - - settingsMap.setKey('fontSize', fontSize)} - min={10} - max={40} - step={2} - /> - -
- - settingsMap.setKey('keybindings', keybindings)} - items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }} - > - - - settingsMap.setKey('panelPosition', value)} - items={{ bottom: 'Bottom', right: 'Right' }} - > - - - settingsMap.setKey('isLineNumbersDisplayed', cbEvent.target.checked)} - value={isLineNumbersDisplayed} - /> - settingsMap.setKey('isAutoCompletionEnabled', cbEvent.target.checked)} - value={isAutoCompletionEnabled} - /> - settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)} - value={isLineWrappingEnabled} - /> - - Try clicking the logo in the top left! - - - -
- ); -} diff --git a/website/src/repl/panel/ConsoleTab.jsx b/website/src/repl/panel/ConsoleTab.jsx new file mode 100644 index 00000000..4472fd4c --- /dev/null +++ b/website/src/repl/panel/ConsoleTab.jsx @@ -0,0 +1,45 @@ +import { cx } from '@strudel.cycles/react'; +import React from 'react'; + +export function ConsoleTab({ log }) { + return ( +
+
{`β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—     
+β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘     
+β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘     
+β•šβ•β•β•β•β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘     
+β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
+β•šβ•β•β•β•β•β•β•   β•šβ•β•   β•šβ•β•  β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•`}
+ {log.map((l, i) => { + const message = linkify(l.message); + return ( +
+ + {l.count ? ` (${l.count})` : ''} +
+ ); + })} +
+ ); +} + +function linkify(inputText) { + var replacedText, replacePattern1, replacePattern2, replacePattern3; + + //URLs starting with http://, https://, or ftp:// + replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; + replacedText = inputText.replace(replacePattern1, '$1'); + + //URLs starting with "www." (without // before it, or it'd re-link the ones done above). + replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; + replacedText = replacedText.replace( + replacePattern2, + '$1$2', + ); + + //Change email addresses to mailto:: links. + replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; + replacedText = replacedText.replace(replacePattern3, '$1'); + + return replacedText; +} diff --git a/website/src/repl/FilesTab.jsx b/website/src/repl/panel/FilesTab.jsx similarity index 97% rename from website/src/repl/FilesTab.jsx rename to website/src/repl/panel/FilesTab.jsx index e04086b6..d78ec1ec 100644 --- a/website/src/repl/FilesTab.jsx +++ b/website/src/repl/panel/FilesTab.jsx @@ -1,6 +1,6 @@ import { Fragment, useEffect } from 'react'; import React, { useMemo, useState } from 'react'; -import { isAudioFile, readDir, dir, playFile } from './files.mjs'; +import { isAudioFile, readDir, dir, playFile } from '../files.mjs'; export function FilesTab() { const [path, setPath] = useState([]); diff --git a/website/src/repl/panel/Forms.jsx b/website/src/repl/panel/Forms.jsx new file mode 100644 index 00000000..e45305e5 --- /dev/null +++ b/website/src/repl/panel/Forms.jsx @@ -0,0 +1,24 @@ +import { cx } from '@strudel.cycles/react'; +import React from 'react'; + +export function ButtonGroup({ value, onChange, items }) { + return ( +
+ {Object.entries(items).map(([key, label], i, arr) => ( + + ))} +
+ ); +} diff --git a/website/src/repl/Reference.jsx b/website/src/repl/panel/Reference.jsx similarity index 97% rename from website/src/repl/Reference.jsx rename to website/src/repl/panel/Reference.jsx index de52982e..6c5d7d99 100644 --- a/website/src/repl/Reference.jsx +++ b/website/src/repl/panel/Reference.jsx @@ -1,4 +1,4 @@ -import jsdocJson from '../../../doc.json'; +import jsdocJson from '../../../../doc.json'; const visibleFunctions = jsdocJson.docs .filter(({ name, description }) => name && !name.startsWith('_') && !!description) .sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name)); diff --git a/website/src/repl/panel/SettingsTab.jsx b/website/src/repl/panel/SettingsTab.jsx new file mode 100644 index 00000000..5dea6258 --- /dev/null +++ b/website/src/repl/panel/SettingsTab.jsx @@ -0,0 +1,173 @@ +import React from 'react'; +import { defaultSettings, settingsMap, useSettings } from '../../settings.mjs'; +import { themes } from '../themes.mjs'; +import { ButtonGroup } from './Forms.jsx'; + +function Checkbox({ label, value, onChange }) { + return ( + + ); +} + +function SelectInput({ value, options, onChange }) { + return ( + + ); +} + +function NumberSlider({ value, onChange, step = 1, ...rest }) { + return ( +
+ onChange(Number(e.target.value))} + {...rest} + /> + onChange(Number(e.target.value))} + /> +
+ ); +} + +function FormItem({ label, children }) { + return ( +
+ + {children} +
+ ); +} + +const themeOptions = Object.fromEntries(Object.keys(themes).map((k) => [k, k])); +const fontFamilyOptions = { + monospace: 'monospace', + BigBlueTerminal: 'BigBlueTerminal', + x3270: 'x3270', + PressStart: 'PressStart2P', + galactico: 'galactico', + 'we-come-in-peace': 'we-come-in-peace', + FiraCode: 'FiraCode', + 'FiraCode-SemiBold': 'FiraCode SemiBold', +}; + +export function SettingsTab() { + const { + theme, + keybindings, + isLineNumbersDisplayed, + isAutoCompletionEnabled, + isLineWrappingEnabled, + fontSize, + fontFamily, + panelPosition, + } = useSettings(); + + return ( +
+ {/* +
+ + +
+
*/} + + settingsMap.setKey('theme', theme)} /> + +
+ + settingsMap.setKey('fontFamily', fontFamily)} + /> + + + settingsMap.setKey('fontSize', fontSize)} + min={10} + max={40} + step={2} + /> + +
+ + settingsMap.setKey('keybindings', keybindings)} + items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }} + > + + + settingsMap.setKey('panelPosition', value)} + items={{ bottom: 'Bottom', right: 'Right' }} + > + + + settingsMap.setKey('isLineNumbersDisplayed', cbEvent.target.checked)} + value={isLineNumbersDisplayed} + /> + settingsMap.setKey('isAutoCompletionEnabled', cbEvent.target.checked)} + value={isAutoCompletionEnabled} + /> + settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)} + value={isLineWrappingEnabled} + /> + + Try clicking the logo in the top left! + + + +
+ ); +} diff --git a/website/src/repl/panel/SoundsTab.jsx b/website/src/repl/panel/SoundsTab.jsx new file mode 100644 index 00000000..b7fbb542 --- /dev/null +++ b/website/src/repl/panel/SoundsTab.jsx @@ -0,0 +1,89 @@ +import { useEvent } from '@strudel.cycles/react'; +// import { cx } from '@strudel.cycles/react'; +import { useStore } from '@nanostores/react'; +import { getAudioContext, soundMap } from '@strudel.cycles/webaudio'; +import React, { useMemo, useRef } from 'react'; +import { settingsMap, useSettings } from '../../settings.mjs'; +import { ButtonGroup } from './Forms.jsx'; + +const getSamples = (samples) => + Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1; + +export function SoundsTab() { + const sounds = useStore(soundMap); + const { soundsFilter } = useSettings(); + const soundEntries = useMemo(() => { + let filtered = Object.entries(sounds).filter(([key]) => !key.startsWith('_')); + if (!sounds) { + return []; + } + if (soundsFilter === 'user') { + return filtered.filter(([key, { data }]) => !data.prebake); + } + if (soundsFilter === 'drums') { + return filtered.filter(([_, { data }]) => data.type === 'sample' && data.tag === 'drum-machines'); + } + if (soundsFilter === 'samples') { + return filtered.filter(([_, { data }]) => data.type === 'sample' && data.tag !== 'drum-machines'); + } + if (soundsFilter === 'synths') { + return filtered.filter(([_, { data }]) => ['synth', 'soundfont'].includes(data.type)); + } + return filtered; + }, [sounds, soundsFilter]); + // holds mutable ref to current triggered sound + const trigRef = useRef(); + // stop current sound on mouseup + useEvent('mouseup', () => { + const t = trigRef.current; + trigRef.current = undefined; + t?.then((ref) => { + ref?.stop(getAudioContext().currentTime + 0.01); + }); + }); + return ( +
+
+ settingsMap.setKey('soundsFilter', value)} + items={{ + samples: 'samples', + drums: 'drum-machines', + synths: 'Synths', + user: 'User', + }} + > +
+
+ {soundEntries.map(([name, { data, onTrigger }]) => ( + { + const ctx = getAudioContext(); + const params = { + note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined, + s: name, + clip: 1, + release: 0.5, + }; + const time = ctx.currentTime + 0.05; + const onended = () => trigRef.current?.node?.disconnect(); + trigRef.current = Promise.resolve(onTrigger(time, params, onended)); + trigRef.current.then((ref) => { + ref?.node.connect(ctx.destination); + }); + }} + > + {' '} + {name} + {data?.type === 'sample' ? `(${getSamples(data.samples)})` : ''} + {data?.type === 'soundfont' ? `(${data.fonts.length})` : ''} + + ))} + {!soundEntries.length ? 'No custom sounds loaded in this pattern (yet).' : ''} +
+
+ ); +} diff --git a/website/src/repl/panel/WelcomeTab.jsx b/website/src/repl/panel/WelcomeTab.jsx new file mode 100644 index 00000000..aca5a813 --- /dev/null +++ b/website/src/repl/panel/WelcomeTab.jsx @@ -0,0 +1,49 @@ +import { cx } from '@strudel.cycles/react'; +import React from 'react'; + +export function WelcomeTab() { + return ( +
+

+ πŸŒ€ welcome +

+

+ You have found strudel, a new live coding platform to write dynamic music + pieces in the browser! It is free and open-source and made for beginners and experts alike. To get started: +
+
+ 1. hit play - 2. change something -{' '} + 3. hit update +
+ If you don't like what you hear, try shuffle! +

+

+ To learn more about what this all means, check out the{' '} + + interactive tutorial + + . Also feel free to join the{' '} + + tidalcycles discord channel + {' '} + to ask any questions, give feedback or just say hello. +

+

about

+

+ strudel is a JavaScript version of{' '} + + tidalcycles + + , which is a popular live coding language for music, written in Haskell. You can find the source code at{' '} + + github + + . Please consider to{' '} + + support this project + {' '} + to ensure ongoing development πŸ’– +

+
+ ); +} From 4e7e715f10dbeb077ce6dba9dc3ba60ef1e2171d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 19 Aug 2023 00:20:46 +0200 Subject: [PATCH 02/72] rename footer -> panel --- website/src/repl/Repl.jsx | 6 +++--- website/src/repl/{Footer.jsx => panel/Panel.jsx} | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) rename website/src/repl/{Footer.jsx => panel/Panel.jsx} (91%) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index aa83317d..1e90f2fb 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -19,7 +19,7 @@ import { createClient } from '@supabase/supabase-js'; import { nanoid } from 'nanoid'; import React, { createContext, useCallback, useEffect, useState, useMemo } from 'react'; import './Repl.css'; -import { Footer } from './Footer'; +import { Panel } from './panel/Panel'; import { Header } from './Header'; import { prebake } from './prebake.mjs'; import * as tunes from './tunes.mjs'; @@ -321,12 +321,12 @@ export function Repl({ embedded = false }) { onSelectionChange={handleSelectionChange} /> - {panelPosition === 'right' && !isEmbedded &&