jevstrudel.git / website / src / settings.mjs
1import { persistentMap } from '@nanostores/persistent';
2import { useStore } from '@nanostores/react';
3import { register } from '@strudel/core';
4import { isUdels } from './repl/util.mjs';
5import { computed } from 'nanostores';
6
7export const audioEngineTargets = {
8  webaudio: 'webaudio',
9  osc: 'osc',
10};
11
12export const soundFilterType = {
13  USER: 'user',
14  DRUMS: 'drums',
15  SAMPLES: 'samples',
16  SYNTHS: 'synths',
17  WAVETABLES: 'wavetables',
18  ALL: 'all',
19};
20
21const initialPrebakeScript = `// Prebake script
22//
23// This is code that is loaded before your pattern is run.
24// You can use it to define custom functions to use in any pattern.
25// 
26// This is an initial example script. You can edit it to add 
27// your own funtions.
28//
29// To use a script shared by some other user you can use
30// the import-button or paste the script in this editor.
31
32const ratchet = register('ratchet', (pat) => pat.sometimes(ply(2)))
33`;
34
35export const defaultSettings = {
36  activeFooter: 'intro',
37  keybindings: 'codemirror',
38  isBracketMatchingEnabled: true,
39  isBracketClosingEnabled: true,
40  isLineNumbersDisplayed: true,
41  isActiveLineHighlighted: true,
42  isAutoCompletionEnabled: false,
43  isTooltipEnabled: false,
44  isFlashEnabled: true,
45  isSyncEnabled: false,
46  isLineWrappingEnabled: false,
47  // jevstrudel: the minimap, with the playing lines on it
48  isMinimapEnabled: true,
49  isPatternHighlightingEnabled: true,
50  isTabIndentationEnabled: false,
51  isMultiCursorEnabled: false,
52  isBlockBasedEvalEnabled: false,
53  theme: 'strudelTheme',
54  fontFamily: 'monospace',
55  fontSize: 18,
56  latestCode: '',
57  isZen: false,
58  soundsFilter: soundFilterType.ALL,
59  referenceTag: 'all',
60  patternFilter: 'community',
61  // panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
62  panelPosition: 'right',
63  isPanelPinned: false,
64  // jevstrudel: a first visit opens the jev panel (the songs) and leaves the editor room
65  isPanelOpen: false,
66  // jevstrudel: the left panel, Jev's (songs and who is listening), and its tab
67  isJevPanelOpen: true,
68  jevTab: 'songs',
69  userPatterns: '{}',
70  prebakeScript: initialPrebakeScript,
71  audioEngineTarget: audioEngineTargets.webaudio,
72  isButtonRowHidden: false,
73  isCSSAnimationDisabled: false,
74  maxPolyphony: 128,
75  multiChannelOrbits: false,
76  includePrebakeScriptInShare: true,
77  settingsTab: 'settings',
78};
79
80let search = null;
81if (typeof window !== 'undefined') {
82  search = new URLSearchParams(window.location.search);
83}

if running multiple instance in one window, it will use the settings for that instance. else default to normal

85const instance = parseInt(search?.get('instance') ?? '0');
86const settings_key = `strudel-settings${instance > 0 ? instance : ''}`;
88export const settingsMap = persistentMap(settings_key, defaultSettings);
89
90export const $settings = computed(settingsMap, (state) => {
91  const userPatterns = JSON.parse(state.userPatterns);
92  Object.keys(userPatterns).forEach((key) => {
93    const data = userPatterns[key];
94    data.id = data.id ?? key;
95    userPatterns[key] = data;
96  });
97  return {
98    ...state,
99    isZen: parseBoolean(state.isZen),
100    isBracketMatchingEnabled: parseBoolean(state.isBracketMatchingEnabled),
101    isBracketClosingEnabled: parseBoolean(state.isBracketClosingEnabled),
102    isLineNumbersDisplayed: parseBoolean(state.isLineNumbersDisplayed),
103    isActiveLineHighlighted: parseBoolean(state.isActiveLineHighlighted),
104    isAutoCompletionEnabled: parseBoolean(state.isAutoCompletionEnabled),
105    isPatternHighlightingEnabled: parseBoolean(state.isPatternHighlightingEnabled),
106    isButtonRowHidden: parseBoolean(state.isButtonRowHidden),
107    isCSSAnimationDisabled: parseBoolean(state.isCSSAnimationDisabled),
108    isTooltipEnabled: parseBoolean(state.isTooltipEnabled),
109    isLineWrappingEnabled: parseBoolean(state.isLineWrappingEnabled),
110    isMinimapEnabled: parseBoolean(state.isMinimapEnabled),
111    isFlashEnabled: parseBoolean(state.isFlashEnabled),
112    isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled),
113    isTabIndentationEnabled: parseBoolean(state.isTabIndentationEnabled),
114    isMultiCursorEnabled: parseBoolean(state.isMultiCursorEnabled),
115    isBlockBasedEvalEnabled: parseBoolean(state.isBlockBasedEvalEnabled),
116    fontSize: Number(state.fontSize),
117    panelPosition: state.activeFooter !== '' && !isUdels() ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
118    isPanelPinned: parseBoolean(state.isPanelPinned),
119    isPanelOpen: parseBoolean(state.isPanelOpen),
120    isJevPanelOpen: parseBoolean(state.isJevPanelOpen),
121    userPatterns: userPatterns,
122    multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
123    includePrebakeScriptInShare: parseBoolean(state.includePrebakeScriptInShare),
124    patternAutoStart: isUdels()
125      ? false
126      : state.patternAutoStart === undefined
127        ? true
128        : parseBoolean(state.patternAutoStart),
129  };
130});
131
132export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
133
134export function useSettings() {
135  return useStore($settings);
136}
137
138export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
139export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool);
140export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool);
141export const setIsJevPanelOpened = (bool) => settingsMap.setKey('isJevPanelOpen', bool);
142export const setJevTab = (tab) => settingsMap.setKey('jevTab', tab);
143export const setSettingsTab = (tab) => settingsMap.setKey('settingsTab', tab);
144
145export const storePrebakeScript = (script) => settingsMap.setKey('prebakeScript', script);
146
147export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
148
149const patternSetting = (key) =>
150  register(key, (value, pat) =>
151    pat.onTrigger(() => {
152      value = Array.isArray(value) ? value.join(' ') : value;
153      if (value !== settingsMap.get()[key]) {
154        settingsMap.setKey(key, value);
155      }
156      return pat;
157    }, false),
158  );
159
160export const theme = patternSetting('theme');
161export const fontFamily = patternSetting('fontFamily');
162export const fontSize = patternSetting('fontSize');
163
164export const settingPatterns = { theme, fontFamily, fontSize };