mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-19 02:34:22 -04:00
refactor settings to nanostores
This commit is contained in:
@@ -47,37 +47,34 @@ const base = BASE_URL;
|
||||
|
||||
<script>
|
||||
import { settings } from '../repl/themes.mjs';
|
||||
import { watch, get } from '../store.mjs';
|
||||
import { settingsMap } from '../settings.mjs';
|
||||
import { listenKeys } from 'nanostores';
|
||||
const themeStyle = document.createElement('style');
|
||||
themeStyle.id = 'strudel-theme';
|
||||
document.head.append(themeStyle);
|
||||
function getTheme(name) {
|
||||
function activateTheme(name) {
|
||||
if (!settings[name]) {
|
||||
console.warn('theme', name, 'has no settings');
|
||||
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
|
||||
}
|
||||
return {
|
||||
name,
|
||||
settings: settings[name] || settings.strudelTheme,
|
||||
};
|
||||
}
|
||||
function setTheme(name) {
|
||||
const { settings } = getTheme(name);
|
||||
const themeSettings = settings[name] || settings.strudelTheme;
|
||||
// set css variables
|
||||
themeStyle.innerHTML = `:root {
|
||||
${Object.entries(settings)
|
||||
${Object.entries(themeSettings)
|
||||
// important to override fallback
|
||||
.map(([key, value]) => `--${key}: ${value} !important;`)
|
||||
.join('\n')}
|
||||
}`;
|
||||
// tailwind dark mode
|
||||
if (settings.light) {
|
||||
if (themeSettings.light) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
}
|
||||
setTheme(get().theme);
|
||||
watch(setTheme, 'theme');
|
||||
|
||||
activateTheme(settingsMap.get().theme);
|
||||
listenKeys(settingsMap, ['theme'], ({ theme }) => activateTheme(theme));
|
||||
|
||||
// https://medium.com/quick-code/100vh-problem-with-ios-safari-92ab23c852a8
|
||||
const appHeight = () => {
|
||||
const doc = document.documentElement;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
|
||||
import { prebake } from '../repl/prebake';
|
||||
import { themes, settings } from '../repl/themes.mjs';
|
||||
import './MiniRepl.css';
|
||||
import useStore from '../useStore.mjs';
|
||||
import { useSettings } from '../settings.mjs';
|
||||
|
||||
let modules;
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -28,9 +28,7 @@ if (typeof window !== 'undefined') {
|
||||
|
||||
export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||
const [Repl, setRepl] = useState();
|
||||
const {
|
||||
state: { theme },
|
||||
} = useStore();
|
||||
const { theme } = useSettings();
|
||||
useEffect(() => {
|
||||
// we have to load this package on the client
|
||||
// because codemirror throws an error on the server
|
||||
|
||||
@@ -7,7 +7,7 @@ import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { loadedSamples } from './Repl';
|
||||
import { Reference } from './Reference';
|
||||
import { themes } from './themes.mjs';
|
||||
import useStore from '../useStore.mjs';
|
||||
import { useSettings, settingsMap } from '../settings.mjs';
|
||||
|
||||
export function Footer({ context }) {
|
||||
// const [activeFooter, setActiveFooter] = useState('console');
|
||||
@@ -287,29 +287,24 @@ const fontFamilyOptions = {
|
||||
};
|
||||
|
||||
function SettingsTab() {
|
||||
const { state, update, reset } = useStore();
|
||||
const { theme, keybindings, fontSize, fontFamily } = state;
|
||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||
return (
|
||||
<div className="text-foreground p-4 space-y-4">
|
||||
<FormItem label="Theme">
|
||||
<SelectInput
|
||||
options={themeOptions}
|
||||
value={theme}
|
||||
onChange={(theme) => update((current) => ({ ...current, theme }))}
|
||||
/>
|
||||
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
|
||||
</FormItem>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormItem label="Font Family">
|
||||
<SelectInput
|
||||
options={fontFamilyOptions}
|
||||
value={fontFamily}
|
||||
onChange={(fontFamily) => update((current) => ({ ...current, fontFamily }))}
|
||||
onChange={(fontFamily) => settingsMap.setKey('fontFamily', fontFamily)}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="Font Size">
|
||||
<NumberSlider
|
||||
value={fontSize}
|
||||
onChange={(fontSize) => update((current) => ({ ...current, fontSize }))}
|
||||
onChange={(fontSize) => settingsMap.setKey('fontSize', fontSize)}
|
||||
min={10}
|
||||
max={40}
|
||||
step={2}
|
||||
@@ -319,7 +314,7 @@ function SettingsTab() {
|
||||
<FormItem label="Keybindings">
|
||||
<ButtonGroup
|
||||
value={keybindings}
|
||||
onChange={(keybindings) => update((current) => ({ ...current, keybindings }))}
|
||||
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
||||
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }}
|
||||
></ButtonGroup>
|
||||
</FormItem>
|
||||
@@ -328,7 +323,7 @@ function SettingsTab() {
|
||||
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
|
||||
onClick={() => {
|
||||
if (confirm('Sure?')) {
|
||||
reset();
|
||||
settingsMap.setKey(defaultSettings);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -23,8 +23,7 @@ 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 useTheme from '../useTheme';
|
||||
import useStore from '../useStore.mjs';
|
||||
import { useSettings } from '../settings.mjs';
|
||||
|
||||
const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme';
|
||||
|
||||
@@ -119,10 +118,7 @@ export function Repl({ embedded = false }) {
|
||||
const [isZen, setIsZen] = useState(false);
|
||||
const [pending, setPending] = useState(false);
|
||||
|
||||
const { theme, themeSettings } = useTheme();
|
||||
const {
|
||||
state: { keybindings, fontSize, fontFamily },
|
||||
} = useStore();
|
||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||
|
||||
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
||||
useStrudel({
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { persistentMap } from '@nanostores/persistent';
|
||||
import { useStore } from '@nanostores/react';
|
||||
|
||||
export const defaultSettings = {
|
||||
keybindings: 'codemirror',
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
};
|
||||
|
||||
export const settingsMap = persistentMap('strudel-settings', defaultSettings);
|
||||
|
||||
export function useSettings() {
|
||||
return useStore(settingsMap);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
export const storeKey = 'strudel-settings';
|
||||
const defaults = {
|
||||
keybindings: 'codemirror',
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
};
|
||||
|
||||
export function get(prop) {
|
||||
let state = {
|
||||
...defaults,
|
||||
...JSON.parse(localStorage.getItem(storeKey) || '{}'),
|
||||
};
|
||||
if (!prop) {
|
||||
return state;
|
||||
}
|
||||
return state[prop];
|
||||
}
|
||||
|
||||
export function set(next) {
|
||||
localStorage.setItem(storeKey, JSON.stringify(next));
|
||||
}
|
||||
|
||||
export function update(func) {
|
||||
const prev = get();
|
||||
const next = func(prev);
|
||||
set(next);
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(storeKey, {
|
||||
detail: { next, prev },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
update(() => defaults);
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
// import { useEvent } from '@strudel.cycles/react';
|
||||
import * as Store from './store.mjs';
|
||||
import {} from 'react';
|
||||
|
||||
function useStore() {
|
||||
const [state, setState] = useState(Store.get());
|
||||
useEvent(Store.storeKey, (e) => setState(e.detail.next));
|
||||
return { state, ...Store };
|
||||
}
|
||||
|
||||
// TODO: dedupe
|
||||
function useEvent(name, onTrigger, useCapture = false) {
|
||||
useEffect(() => {
|
||||
document.addEventListener(name, onTrigger, useCapture);
|
||||
return () => {
|
||||
document.removeEventListener(name, onTrigger, useCapture);
|
||||
};
|
||||
}, [onTrigger]);
|
||||
}
|
||||
|
||||
export default useStore;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { settings } from './repl/themes.mjs';
|
||||
import useStore from './useStore.mjs';
|
||||
|
||||
function useTheme() {
|
||||
const { state } = useStore();
|
||||
const theme = state.theme || 'strudelTheme';
|
||||
const themeSettings = settings[theme];
|
||||
return {
|
||||
theme: state.theme,
|
||||
themeSettings,
|
||||
};
|
||||
}
|
||||
|
||||
export default useTheme;
|
||||
Reference in New Issue
Block a user