From 014555fe5ddf4b82249ec557e685b525a15c1ad0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 19 Feb 2023 01:51:31 +0100 Subject: [PATCH] add vim toggle to settings + added persistent global state store + refactored themes to use the new store --- packages/react/src/components/CodeMirror6.jsx | 7 +- pnpm-lock.yaml | 74 +++++-------------- website/package.json | 1 + website/public/store.mjs | 36 +++++++++ website/src/components/HeadCommon.astro | 9 +-- website/src/docs/MiniRepl.jsx | 1 - website/src/repl/Footer.jsx | 60 ++++++++------- website/src/repl/Repl.jsx | 14 ++-- website/src/useStore.mjs | 11 +++ website/src/useTheme.jsx | 25 ++----- website/tailwind.config.cjs | 2 +- 11 files changed, 120 insertions(+), 120 deletions(-) create mode 100644 website/public/store.mjs create mode 100644 website/src/useStore.mjs diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx index 72e266f8..c04f422a 100644 --- a/packages/react/src/components/CodeMirror6.jsx +++ b/packages/react/src/components/CodeMirror6.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import _CodeMirror from '@uiw/react-codemirror'; import { EditorView, Decoration } from '@codemirror/view'; import { StateField, StateEffect } from '@codemirror/state'; @@ -83,9 +83,8 @@ const highlightField = StateField.define({ provide: (f) => EditorView.decorations.from(f), }); -const extensions = [ +const staticExtensions = [ javascript(), - vim(), highlightField, flashField, // javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }), @@ -99,6 +98,7 @@ export default function CodeMirror({ onViewChanged, onSelectionChange, theme, + vimMode, options, editorDidMount, }) { @@ -122,6 +122,7 @@ export default function CodeMirror({ }, [onSelectionChange], ); + const extensions = useMemo(() => [...staticExtensions, ...(vimMode ? [vim()] : [])], [vimMode]); return ( <> <_CodeMirror diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90cda99a..5b8e36d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -373,6 +373,7 @@ importers: '@strudel.cycles/webaudio': workspace:* '@strudel.cycles/xen': workspace:* '@supabase/supabase-js': ^1.35.3 + '@tailwindcss/forms': ^0.5.3 '@tailwindcss/typography': ^0.5.8 '@types/node': ^18.0.0 '@types/react': ^18.0.26 @@ -417,6 +418,7 @@ importers: '@strudel.cycles/webaudio': link:../packages/webaudio '@strudel.cycles/xen': link:../packages/xen '@supabase/supabase-js': 1.35.7 + '@tailwindcss/forms': 0.5.3_tailwindcss@3.2.4 '@tailwindcss/typography': 0.5.9_tailwindcss@3.2.4 '@types/node': 18.11.18 '@types/react': 18.0.27 @@ -3575,6 +3577,15 @@ packages: string.prototype.matchall: 4.0.8 dev: true + /@tailwindcss/forms/0.5.3_tailwindcss@3.2.4: + resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.2.4 + dev: false + /@tailwindcss/typography/0.5.9_tailwindcss@3.2.4: resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==} peerDependencies: @@ -9386,6 +9397,11 @@ packages: engines: {node: '>=4'} dev: true + /mini-svg-data-uri/1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + dev: false + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -10428,17 +10444,6 @@ packages: - supports-color dev: true - /postcss-import/14.1.0: - resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} - engines: {node: '>=10.0.0'} - peerDependencies: - postcss: ^8.0.0 - dependencies: - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.1 - dev: false - /postcss-import/14.1.0_postcss@8.4.21: resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} @@ -10449,16 +10454,6 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.1 - dev: true - - /postcss-js/4.0.0: - resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.3.3 - dependencies: - camelcase-css: 2.0.1 - dev: false /postcss-js/4.0.0_postcss@8.4.21: resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} @@ -10468,23 +10463,6 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.21 - dev: true - - /postcss-load-config/3.1.4: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 2.0.6 - yaml: 1.10.2 - dev: false /postcss-load-config/3.1.4_postcss@8.4.21: resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} @@ -10502,15 +10480,6 @@ packages: postcss: 8.4.21 yaml: 1.10.2 - /postcss-nested/6.0.0: - resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - dependencies: - postcss-selector-parser: 6.0.11 - dev: false - /postcss-nested/6.0.0_postcss@8.4.21: resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} engines: {node: '>=12.0'} @@ -10519,7 +10488,6 @@ packages: dependencies: postcss: 8.4.21 postcss-selector-parser: 6.0.11 - dev: true /postcss-selector-parser/6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} @@ -12142,8 +12110,6 @@ packages: resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==} engines: {node: '>=12.13.0'} hasBin: true - peerDependencies: - postcss: ^8.0.9 dependencies: arg: 5.0.2 chokidar: 3.5.3 @@ -12160,10 +12126,10 @@ packages: object-hash: 3.0.0 picocolors: 1.0.0 postcss: 8.4.21 - postcss-import: 14.1.0 - postcss-js: 4.0.0 - postcss-load-config: 3.1.4 - postcss-nested: 6.0.0 + postcss-import: 14.1.0_postcss@8.4.21 + postcss-js: 4.0.0_postcss@8.4.21 + postcss-load-config: 3.1.4_postcss@8.4.21 + postcss-nested: 6.0.0_postcss@8.4.21 postcss-selector-parser: 6.0.11 postcss-value-parser: 4.2.0 quick-lru: 5.1.1 diff --git a/website/package.json b/website/package.json index f8c466d3..cf73f8cb 100644 --- a/website/package.json +++ b/website/package.json @@ -34,6 +34,7 @@ "@strudel.cycles/webaudio": "workspace:*", "@strudel.cycles/xen": "workspace:*", "@supabase/supabase-js": "^1.35.3", + "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.8", "@types/node": "^18.0.0", "@types/react": "^18.0.26", diff --git a/website/public/store.mjs b/website/public/store.mjs new file mode 100644 index 00000000..1efcd91b --- /dev/null +++ b/website/public/store.mjs @@ -0,0 +1,36 @@ +export const storeKey = 'strudel-settings'; + +export function get(prop) { + const state = JSON.parse(localStorage.getItem(storeKey)); + if (!prop) { + return state; + } + return state[prop]; +} + +export function set(next) { + localStorage.setItem(storeKey, JSON.stringify(next)); +} + +export function updateState(func) { + const prev = get(); + const next = func(prev); + set(next); + document.dispatchEvent( + new CustomEvent(storeKey, { + detail: { next, prev }, + }), + ); +} + +export function watch(func, prop) { + document.addEventListener(storeKey, (e) => { + const { prev, next } = e.detail; + const hasPropChanged = (p) => next[p] !== prev[p]; + if (!prop) { + func(next); + } else if (hasPropChanged(prop)) { + func(next[prop]); + } + }); +} diff --git a/website/src/components/HeadCommon.astro b/website/src/components/HeadCommon.astro index 796f96db..dcf9af3b 100644 --- a/website/src/components/HeadCommon.astro +++ b/website/src/components/HeadCommon.astro @@ -48,7 +48,8 @@ const { strudelTheme } = settings; {pwaInfo && } - diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index f3e83d99..51e48915 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -36,7 +36,6 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) { .then(([res]) => setRepl(() => res.MiniRepl)) .catch((err) => console.error(err)); }, []); - // const { settings } = useTheme(); return Repl ? (
} {activeFooter === 'samples' && } {activeFooter === 'reference' && } - {activeFooter === 'settings' && } + {activeFooter === 'settings' && }
)} @@ -205,37 +206,34 @@ function SamplesTab() { ); } -function SettingsTab({ theme, setTheme }) { - /*{ - console.log('vim mode toggle', checked) - }}/>*/ - +function SettingsTab() { + const { state, update } = useStore(); + const { theme, vim } = state; return ( -
- {Object.entries(themes).map(([k, t]) => ( -
{ - setTheme(k); - document.dispatchEvent( - new CustomEvent('strudel-theme', { - detail: k, - }), - ); - }} +
+ +
); } diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 818adea9..3b2f306c 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -24,6 +24,7 @@ import * as tunes from './tunes.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; import { themes } from './themes.mjs'; import useTheme from '../useTheme'; +import useStore from '../useStore.mjs'; const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme'; @@ -113,12 +114,16 @@ export const ReplContext = createContext(null); export function Repl({ embedded = false }) { const isEmbedded = embedded || window.location !== window.parent.location; const [view, setView] = useState(); // codemirror view - const [theme, setTheme] = useState(initialTheme); const [lastShared, setLastShared] = useState(); const [activeFooter, setActiveFooter] = useState(''); const [isZen, setIsZen] = useState(false); const [pending, setPending] = useState(false); + const { theme, themeSettings } = useTheme(); + const { + state: { vim }, + } = useStore(); + const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } = useStrudel({ initialCode: '// LOADING', @@ -172,15 +177,13 @@ export function Repl({ embedded = false }) { ), ); - const { settings } = useTheme(); - // highlighting useHighlighting({ view, pattern, active: started && !activeCode?.includes('strudel disable-highlighting'), getTime: () => scheduler.now(), - color: settings?.foreground, + color: themeSettings?.foreground, }); // @@ -263,8 +266,6 @@ export function Repl({ embedded = false }) { handleShare, isZen, setIsZen, - theme, - setTheme, }; return ( // bg-gradient-to-t from-blue-900 to-slate-900 @@ -281,6 +282,7 @@ export function Repl({ embedded = false }) { setState(e.detail.next)); + return { state, update: Store.updateState }; +} + +export default useStore; diff --git a/website/src/useTheme.jsx b/website/src/useTheme.jsx index 51c66cbf..37c51100 100644 --- a/website/src/useTheme.jsx +++ b/website/src/useTheme.jsx @@ -1,27 +1,14 @@ -import { useState } from 'react'; import { settings } from './repl/themes.mjs'; -import { useEffect } from 'react'; +import useStore from './useStore.mjs'; function useTheme() { - const [theme, setTheme] = useState(localStorage.getItem('strudel-theme')); - useEvent('strudel-theme', (e) => setTheme(e.detail)); - const themeSettings = settings[theme || 'strudelTheme']; + const { state } = useStore(); + const theme = state.theme || 'strudelTheme'; + const themeSettings = settings[theme]; return { - theme, - setTheme, - settings: themeSettings, - isDark: !themeSettings.light, - isLight: !!themeSettings.light, + theme: state.theme, + themeSettings, }; } -// TODO: dedupe -function useEvent(name, onTrigger, useCapture = false) { - useEffect(() => { - document.addEventListener(name, onTrigger, useCapture); - return () => { - document.removeEventListener(name, onTrigger, useCapture); - }; - }, [onTrigger]); -} export default useTheme; diff --git a/website/tailwind.config.cjs b/website/tailwind.config.cjs index 877a2acd..f2421598 100644 --- a/website/tailwind.config.cjs +++ b/website/tailwind.config.cjs @@ -41,5 +41,5 @@ module.exports = { }, }, }, - plugins: [require('@tailwindcss/typography')], + plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')], };