From f19ac748e6a7dcff06068b931c60b7cc7d277fe7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 10 Dec 2023 14:28:27 +0100 Subject: [PATCH 1/5] slim down template - move import + export logic to settings.mjs --- website/src/repl/panel/PatternsTab.jsx | 47 +++++--------------------- website/src/settings.mjs | 30 ++++++++++++++++ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 175ed887..103eda19 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -1,20 +1,18 @@ +import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid'; import { useMemo } from 'react'; -import * as tunes from '../tunes.mjs'; import { - useSettings, clearUserPatterns, - newUserPattern, - setActivePattern, deleteActivePattern, duplicateActivePattern, + exportPatterns, getUserPattern, - getUserPatterns, + importPatterns, + newUserPattern, renameActivePattern, - addUserPattern, - setUserPatterns, + setActivePattern, + useSettings, } from '../../settings.mjs'; -import { logger } from '@strudel.cycles/core'; -import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid'; +import * as tunes from '../tunes.mjs'; function classNames(...classes) { return classes.filter(Boolean).join(' '); @@ -85,38 +83,11 @@ export function PatternsTab({ context }) { type="file" multiple accept="text/plain,application/json" - onChange={async (e) => { - const files = Array.from(e.target.files); - await Promise.all( - files.map(async (file, i) => { - const content = await file.text(); - if (file.type === 'application/json') { - const userPatterns = getUserPatterns() || {}; - setUserPatterns({ ...userPatterns, ...JSON.parse(content) }); - } else if (file.type === 'text/plain') { - const name = file.name.replace(/\.[^/.]+$/, ''); - addUserPattern(name, { code: content }); - } - }), - ); - logger(`import done!`); - }} + onChange={(e) => importPatterns(e.target.files)} /> import - diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 98d3fe50..2f76d68c 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -2,6 +2,7 @@ import { persistentMap } from '@nanostores/persistent'; import { useStore } from '@nanostores/react'; import { register } from '@strudel.cycles/core'; import * as tunes from './repl/tunes.mjs'; +import { logger } from '@strudel.cycles/core'; export const defaultSettings = { activeFooter: 'intro', @@ -195,3 +196,32 @@ export function setActivePattern(key) { } export function importUserPatternJSON(jsonString) {} + +export async function importPatterns(fileList) { + const files = Array.from(fileList); + await Promise.all( + files.map(async (file, i) => { + const content = await file.text(); + if (file.type === 'application/json') { + const userPatterns = getUserPatterns() || {}; + setUserPatterns({ ...userPatterns, ...JSON.parse(content) }); + } else if (file.type === 'text/plain') { + const name = file.name.replace(/\.[^/.]+$/, ''); + addUserPattern(name, { code: content }); + } + }), + ); + logger(`import done!`); +} + +export async function exportPatterns() { + const userPatterns = getUserPatterns() || {}; + const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' }); + const downloadLink = document.createElement('a'); + downloadLink.href = window.URL.createObjectURL(blob); + const date = new Date().toISOString().split('T')[0]; + downloadLink.download = `strudel_patterns_${date}.json`; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); +} From 1f16ebc5b25740b46b7f830e688318ca87acba99 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 10 Dec 2023 21:01:51 +0100 Subject: [PATCH 2/5] fix: deselect user pattern when opening links --- website/src/repl/Repl.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index a5bfffd4..4ff92a80 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -177,6 +177,7 @@ export function Repl({ embedded = false }) { let msg; if (decoded) { setCode(decoded); + setActivePattern(''); msg = `I have loaded the code from the URL.`; } else if (latestCode) { setCode(latestCode); From faaed61384caec34fba8c49c7aeb6f5f08f5cf42 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 11 Dec 2023 22:01:08 +0100 Subject: [PATCH 3/5] fix: do not sync activePattern between tabs + only deselect activePattern if code in url differs --- website/src/repl/Repl.jsx | 18 ++++++++++++++--- website/src/repl/panel/PatternsTab.jsx | 4 +++- website/src/settings.mjs | 28 +++++++++++++++++--------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 4ff92a80..32765477 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -17,7 +17,15 @@ import { prebake } from './prebake.mjs'; import * as tunes from './tunes.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; import { themes } from './themes.mjs'; -import { settingsMap, useSettings, setLatestCode, updateUserCode, setActivePattern } from '../settings.mjs'; +import { + settingsMap, + useSettings, + setLatestCode, + updateUserCode, + setActivePattern, + getActivePattern, + getUserPattern, +} from '../settings.mjs'; import Loader from './Loader'; import { settingPatterns } from '../settings.mjs'; import { code2hash, hash2code } from './helpers.mjs'; @@ -131,7 +139,6 @@ export function Repl({ embedded = false }) { isLineWrappingEnabled, panelPosition, isZen, - activePattern, } = useSettings(); const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]); @@ -177,7 +184,12 @@ export function Repl({ embedded = false }) { let msg; if (decoded) { setCode(decoded); - setActivePattern(''); + const activePattern = getActivePattern(); + if (getUserPattern(activePattern)?.code !== decoded) { + // code in url is not the last active patterns code => must be something else + // deselect last active pattern to not overwrite it on next evaluation + setActivePattern(''); + } msg = `I have loaded the code from the URL.`; } else if (latestCode) { setCode(latestCode); diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 103eda19..4466b599 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -10,6 +10,7 @@ import { newUserPattern, renameActivePattern, setActivePattern, + useActivePattern, useSettings, } from '../../settings.mjs'; import * as tunes from '../tunes.mjs'; @@ -19,7 +20,8 @@ function classNames(...classes) { } export function PatternsTab({ context }) { - const { userPatterns, activePattern } = useSettings(); + const { userPatterns } = useSettings(); + const activePattern = useActivePattern(); const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]); return (
diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 2f76d68c..8a85e4a8 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -1,4 +1,4 @@ -import { persistentMap } from '@nanostores/persistent'; +import { persistentMap, persistentAtom } from '@nanostores/persistent'; import { useStore } from '@nanostores/react'; import { register } from '@strudel.cycles/core'; import * as tunes from './repl/tunes.mjs'; @@ -20,11 +20,23 @@ export const defaultSettings = { soundsFilter: 'all', panelPosition: 'bottom', userPatterns: '{}', - activePattern: '', }; export const settingsMap = persistentMap('strudel-settings', defaultSettings); +// active pattern is separate, because it shouldn't sync state across tabs +// reason: https://github.com/tidalcycles/strudel/issues/857 +const $activePattern = persistentAtom('activePattern', '', { listen: false }); +export function setActivePattern(key) { + $activePattern.set(key); +} +export function getActivePattern() { + return $activePattern.get(); +} +export function useActivePattern() { + return useStore($activePattern); +} + export function useSettings() { const state = useStore(settingsMap); return { @@ -117,7 +129,7 @@ export function getUserPattern(key) { } export function renameActivePattern() { - let activePattern = getSetting('activePattern'); + let activePattern = getActivePattern(); let userPatterns = getUserPatterns(); if (!userPatterns[activePattern]) { alert('Cannot rename examples'); @@ -140,7 +152,7 @@ export function renameActivePattern() { export function updateUserCode(code) { const userPatterns = getUserPatterns(); - let activePattern = getSetting('activePattern'); + let activePattern = getActivePattern(); // check if code is that of an example tune const [example] = Object.entries(tunes).find(([_, tune]) => tune === code) || []; if (example && (!activePattern || activePattern === example)) { @@ -161,7 +173,7 @@ export function updateUserCode(code) { } export function deleteActivePattern() { - let activePattern = getSetting('activePattern'); + let activePattern = getActivePattern(); if (!activePattern) { console.warn('cannot delete: no pattern selected'); return; @@ -179,7 +191,7 @@ export function deleteActivePattern() { } export function duplicateActivePattern() { - let activePattern = getSetting('activePattern'); + let activePattern = getActivePattern(); let latestCode = getSetting('latestCode'); if (!activePattern) { console.warn('cannot duplicate: no pattern selected'); @@ -191,10 +203,6 @@ export function duplicateActivePattern() { setActivePattern(activePattern); } -export function setActivePattern(key) { - settingsMap.setKey('activePattern', key); -} - export function importUserPatternJSON(jsonString) {} export async function importPatterns(fileList) { From 853a31b638348e0e6ca5dc0ce86bb2dc98f797e9 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 11 Dec 2023 22:17:10 +0100 Subject: [PATCH 4/5] improve code init logic --- website/src/repl/Repl.jsx | 8 ++------ website/src/settings.mjs | 5 +++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 32765477..db586363 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -25,6 +25,7 @@ import { setActivePattern, getActivePattern, getUserPattern, + initUserCode, } from '../settings.mjs'; import Loader from './Loader'; import { settingPatterns } from '../settings.mjs'; @@ -184,12 +185,7 @@ export function Repl({ embedded = false }) { let msg; if (decoded) { setCode(decoded); - const activePattern = getActivePattern(); - if (getUserPattern(activePattern)?.code !== decoded) { - // code in url is not the last active patterns code => must be something else - // deselect last active pattern to not overwrite it on next evaluation - setActivePattern(''); - } + initUserCode(decoded); msg = `I have loaded the code from the URL.`; } else if (latestCode) { setCode(latestCode); diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 8a85e4a8..59078651 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -36,6 +36,11 @@ export function getActivePattern() { export function useActivePattern() { return useStore($activePattern); } +export function initUserCode(code) { + const userPatterns = getUserPatterns(); + const match = Object.entries(userPatterns).find(([_, pat]) => pat.code === code); + setActivePattern(match?.[0] || ''); +} export function useSettings() { const state = useStore(settingsMap); From f939e00a84fa6260bef8bc2ef4a388910587dee1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 11 Dec 2023 22:46:30 +0100 Subject: [PATCH 5/5] cleanup --- website/src/settings.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 59078651..570b6446 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -208,8 +208,6 @@ export function duplicateActivePattern() { setActivePattern(activePattern); } -export function importUserPatternJSON(jsonString) {} - export async function importPatterns(fileList) { const files = Array.from(fileList); await Promise.all(