mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-26 13:08:28 +00:00
tabbed footer
This commit is contained in:
parent
ff5d14fb63
commit
a27f399a9e
@ -44,3 +44,4 @@ currently broken / buggy:
|
|||||||
- [x] highlighting sometimes drops highlights (zeldasRescue first note)
|
- [x] highlighting sometimes drops highlights (zeldasRescue first note)
|
||||||
- [ ] highlighting still sometimes drops highlights (zeldasRescue somtimes)
|
- [ ] highlighting still sometimes drops highlights (zeldasRescue somtimes)
|
||||||
- [ ] highlighting out of range error is back (delete large chunk at the top while highlighting below is triggered)
|
- [ ] highlighting out of range error is back (delete large chunk at the top while highlighting below is triggered)
|
||||||
|
- [ ] find a way to display errors when console is closed / another tab selected
|
||||||
|
|||||||
102
repl/src/App.jsx
102
repl/src/App.jsx
@ -24,6 +24,7 @@ import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
|
|||||||
import CommandLineIcon from '@heroicons/react/20/solid/CommandLineIcon';
|
import CommandLineIcon from '@heroicons/react/20/solid/CommandLineIcon';
|
||||||
import SparklesIcon from '@heroicons/react/20/solid/SparklesIcon';
|
import SparklesIcon from '@heroicons/react/20/solid/SparklesIcon';
|
||||||
import LinkIcon from '@heroicons/react/20/solid/LinkIcon';
|
import LinkIcon from '@heroicons/react/20/solid/LinkIcon';
|
||||||
|
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
|
||||||
import AcademicCapIcon from '@heroicons/react/20/solid/AcademicCapIcon';
|
import AcademicCapIcon from '@heroicons/react/20/solid/AcademicCapIcon';
|
||||||
|
|
||||||
initAudioOnFirstClick();
|
initAudioOnFirstClick();
|
||||||
@ -98,6 +99,7 @@ const isEmbedded = window.location !== window.parent.location;
|
|||||||
function App() {
|
function App() {
|
||||||
const [view, setView] = useState(); // codemirror view
|
const [view, setView] = useState(); // codemirror view
|
||||||
const [lastShared, setLastShared] = useState();
|
const [lastShared, setLastShared] = useState();
|
||||||
|
const [activeFooter, setActiveFooter] = useState('console');
|
||||||
|
|
||||||
// logger
|
// logger
|
||||||
const [log, setLog] = useState([]);
|
const [log, setLog] = useState([]);
|
||||||
@ -121,11 +123,11 @@ function App() {
|
|||||||
});
|
});
|
||||||
}, []),
|
}, []),
|
||||||
);
|
);
|
||||||
const logBox = useRef();
|
const footerContent = useRef();
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (logBox.current) {
|
if (footerContent.current) {
|
||||||
// scroll log box to bottom when log changes
|
// scroll log box to bottom when log changes
|
||||||
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
footerContent.current.scrollTop = footerContent.current?.scrollHeight;
|
||||||
}
|
}
|
||||||
}, [log]);
|
}, [log]);
|
||||||
|
|
||||||
@ -235,9 +237,30 @@ function App() {
|
|||||||
started && logger('[edit] code changed. hit ctrl+enter to update');
|
started && logger('[edit] code changed. hit ctrl+enter to update');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FooterTab = ({ label, children, type }) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onClick={() => setActiveFooter(type)}
|
||||||
|
className={cx(
|
||||||
|
'h-8 px-2 text-white cursor-pointer hover:text-highlight flex items-center space-x-1',
|
||||||
|
activeFooter === type ? 'border-b' : '',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
{activeFooter === type && <>{children}</>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// bg-gradient-to-t from-blue-900 to-slate-900
|
// bg-gradient-to-t from-blue-900 to-slate-900
|
||||||
<div className="h-screen flex flex-col">
|
// bg-gradient-to-t from-green-900 to-slate-900
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'h-screen flex flex-col',
|
||||||
|
// 'bg-gradient-to-t from-green-900 to-slate-900', //
|
||||||
|
)}
|
||||||
|
>
|
||||||
{!hideHeader && (
|
{!hideHeader && (
|
||||||
<header
|
<header
|
||||||
id="header"
|
id="header"
|
||||||
@ -249,7 +272,7 @@ function App() {
|
|||||||
<div className="px-2 flex items-center space-x-2 pt-2 md:pt-0 pointer-events-none">
|
<div className="px-2 flex items-center space-x-2 pt-2 md:pt-0 pointer-events-none">
|
||||||
<img
|
<img
|
||||||
src={logo}
|
src={logo}
|
||||||
className={cx('Tidal-logo', isEmbedded ? 'w-8 h-8' : 'w-10 h-10')} // 'bg-[#ffffff80] rounded-full'
|
className={cx('Tidal-logo', isEmbedded ? 'w-8 h-8' : 'w-10 h-10', started && 'animate-pulse')} // 'bg-[#ffffff80] rounded-full'
|
||||||
alt="logo"
|
alt="logo"
|
||||||
/>
|
/>
|
||||||
<h1
|
<h1
|
||||||
@ -259,13 +282,13 @@ function App() {
|
|||||||
'text-white font-bold',
|
'text-white font-bold',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
strudel <span className="text-sm">REPL</span>
|
<span className="">strudel</span> <span className="text-sm">REPL</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex max-w-full overflow-auto text-white ">
|
<div className="flex max-w-full overflow-auto text-white ">
|
||||||
<button onClick={handleTogglePlay} className={cx(!isEmbedded ? 'p-2' : 'px-2')}>
|
<button onClick={handleTogglePlay} className={cx(!isEmbedded ? 'p-2' : 'px-2')}>
|
||||||
{!pending ? (
|
{!pending ? (
|
||||||
<span className={cx('flex items-center space-x-1 hover:text-primary', isEmbedded ? 'w-16' : 'w-16')}>
|
<span className={cx('flex items-center space-x-1 hover:text-highlight', isEmbedded ? 'w-16' : 'w-16')}>
|
||||||
{started ? <StopCircleIcon className="w-5 h-5" /> : <PlayCircleIcon className="w-5 h-5" />}
|
{started ? <StopCircleIcon className="w-5 h-5" /> : <PlayCircleIcon className="w-5 h-5" />}
|
||||||
<span>{started ? 'stop' : 'play'}</span>
|
<span>{started ? 'stop' : 'play'}</span>
|
||||||
</span>
|
</span>
|
||||||
@ -278,14 +301,14 @@ function App() {
|
|||||||
className={cx(
|
className={cx(
|
||||||
'flex items-center space-x-1',
|
'flex items-center space-x-1',
|
||||||
!isEmbedded ? 'p-2' : 'px-2',
|
!isEmbedded ? 'p-2' : 'px-2',
|
||||||
!isDirty || !activeCode ? 'opacity-50' : 'hover:text-primary',
|
!isDirty || !activeCode ? 'opacity-50' : 'hover:text-highlight',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CommandLineIcon className="w-5 h-5" />
|
<CommandLineIcon className="w-5 h-5" />
|
||||||
<span>update</span>
|
<span>update</span>
|
||||||
</button>
|
</button>
|
||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<button className="hover:text-primary p-2 flex items-center space-x-1" onClick={handleShuffle}>
|
<button className="hover:text-highlight p-2 flex items-center space-x-1" onClick={handleShuffle}>
|
||||||
<SparklesIcon className="w-5 h-5" />
|
<SparklesIcon className="w-5 h-5" />
|
||||||
<span> shuffle</span>
|
<span> shuffle</span>
|
||||||
</button>
|
</button>
|
||||||
@ -293,7 +316,7 @@ function App() {
|
|||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<a
|
<a
|
||||||
href="./tutorial"
|
href="./tutorial"
|
||||||
className={cx('hover:text-primary flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
className={cx('hover:text-highlight flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||||
>
|
>
|
||||||
<AcademicCapIcon className="w-5 h-5" />
|
<AcademicCapIcon className="w-5 h-5" />
|
||||||
<span>learn</span>
|
<span>learn</span>
|
||||||
@ -302,7 +325,7 @@ function App() {
|
|||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<button
|
<button
|
||||||
className={cx(
|
className={cx(
|
||||||
'cursor-pointer hover:text-primary flex items-center space-x-1',
|
'cursor-pointer hover:text-highlight flex items-center space-x-1',
|
||||||
!isEmbedded ? 'p-2' : 'px-2',
|
!isEmbedded ? 'p-2' : 'px-2',
|
||||||
)}
|
)}
|
||||||
onClick={handleShare}
|
onClick={handleShare}
|
||||||
@ -312,14 +335,14 @@ function App() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isEmbedded && (
|
{isEmbedded && (
|
||||||
<button className={cx('hover:text-primary px-2')}>
|
<button className={cx('hover:text-highlight px-2')}>
|
||||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
|
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
|
||||||
🚀 open
|
🚀 open
|
||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isEmbedded && (
|
{isEmbedded && (
|
||||||
<button className={cx('hover:text-primary px-2')}>
|
<button className={cx('hover:text-highlight px-2')}>
|
||||||
<a
|
<a
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.href = initialUrl;
|
window.location.href = initialUrl;
|
||||||
@ -334,27 +357,46 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
<section className="grow flex text-gray-100 relative overflow-auto cursor-text pb-4" id="code">
|
<section className="grow flex text-gray-100 relative overflow-auto cursor-text pb-0" id="code">
|
||||||
<CodeMirror value={code} onChange={handleChangeCode} onViewChanged={setView} />
|
<CodeMirror value={code} onChange={handleChangeCode} onViewChanged={setView} />
|
||||||
</section>
|
</section>
|
||||||
<footer className="bg-footer">
|
<footer className="bg-footer">
|
||||||
<div
|
<div className="flex justify-between px-2">
|
||||||
ref={logBox}
|
<div className="flex">
|
||||||
className="text-white font-mono text-sm h-64 flex-none overflow-auto max-w-full break-all p-4"
|
<FooterTab type="help" label="Help" />
|
||||||
>
|
<FooterTab type="samples" label="Samples" />
|
||||||
{log.map((l, i) => {
|
<FooterTab type="console" label="Console" />
|
||||||
const message = linkify(l.message);
|
</div>
|
||||||
return (
|
{activeFooter !== '' && (
|
||||||
<div
|
<button onClick={() => setActiveFooter('')} className="text-white">
|
||||||
key={l.id}
|
<XMarkIcon className="w-5 h-5" />
|
||||||
className={cx(l.type === 'error' && 'text-red-500', l.type === 'highlight' && 'text-highlight')}
|
</button>
|
||||||
>
|
)}
|
||||||
> <span dangerouslySetInnerHTML={{ __html: message }} />
|
|
||||||
{l.count ? ` (${l.count})` : ''}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
|
{activeFooter !== '' && (
|
||||||
|
<div
|
||||||
|
className="text-white font-mono text-sm h-64 flex-none overflow-auto max-w-full break-all p-4"
|
||||||
|
ref={footerContent}
|
||||||
|
>
|
||||||
|
{activeFooter === 'console' && (
|
||||||
|
<div>
|
||||||
|
{log.map((l, i) => {
|
||||||
|
const message = linkify(l.message);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={l.id}
|
||||||
|
className={cx(l.type === 'error' && 'text-red-500', l.type === 'highlight' && 'text-highlight')}
|
||||||
|
>
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: message }} />
|
||||||
|
{l.count ? ` (${l.count})` : ''}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeFooter === 'samples' && <div>samples...</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -19,8 +19,8 @@ module.exports = {
|
|||||||
bg: '#222222',
|
bg: '#222222',
|
||||||
// header: '#8a91991a',
|
// header: '#8a91991a',
|
||||||
// footer: '#8a91991a',
|
// footer: '#8a91991a',
|
||||||
// header: '#00000050',
|
header: '#00000050',
|
||||||
header: 'transparent',
|
// header: 'transparent',
|
||||||
footer: '#00000050',
|
footer: '#00000050',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user