diff --git a/packages/codemirror/Autocomplete.jsx b/packages/codemirror/Autocomplete.jsx new file mode 100644 index 000000000..cbc7b666f --- /dev/null +++ b/packages/codemirror/Autocomplete.jsx @@ -0,0 +1,85 @@ +import { createRoot } from 'react-dom/client'; +import jsdoc from '../../doc.json'; +import { javascriptLanguage } from '@codemirror/lang-javascript'; +import { autocompletion } from '@codemirror/autocomplete'; + +const getDocLabel = (doc) => doc.name || doc.longname; +const getInnerText = (html) => { + var div = document.createElement('div'); + div.innerHTML = html; + return div.textContent || div.innerText || ''; +}; + +export function Autocomplete({ doc }) { + return ( +
+

{getDocLabel(doc)}

+
+ +
+ {doc.examples?.map((example, i) => ( +
+
 {
+                console.log('ola!');
+                navigator.clipboard.writeText(example);
+                e.stopPropagation();
+              }}
+            >
+              {example}
+            
+
+ ))} +
+
+ ); +} + +const jsdocCompletions = jsdoc.docs + .filter( + (doc) => + getDocLabel(doc) && + !getDocLabel(doc).startsWith('_') && + !['package'].includes(doc.kind) && + !['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)), + ) + // https://codemirror.net/docs/ref/#autocomplete.Completion + .map((doc) /*: Completion */ => ({ + label: getDocLabel(doc), + // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. + info: () => { + const node = document.createElement('div'); + // if Autocomplete is non-interactive, it could also be rendered at build time.. + // .. using renderToStaticMarkup + createRoot(node).render(); + return node; + }, + type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type + })); + +export const strudelAutocomplete = (context /* : CompletionContext */) => { + let word = context.matchBefore(/\w*/); + console.log('word', word); + if (word.from == word.to && !context.explicit) return null; + return { + from: word.from, + options: jsdocCompletions, + /* options: [ + { label: 'match', type: 'keyword' }, + { label: 'hello', type: 'variable', info: '(World)' }, + { label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' }, + ], */ + }; +}; + +export function isAutoCompletionEnabled(on) { + console.log('autocomplete on',on); + return on ? javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }) : autocompletion({ override: [] }); +} diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index aa90d7768..22b91f92f 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -4,25 +4,37 @@ import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language' import { EditorState, Compartment } from '@codemirror/state'; import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view'; import { Drawer, repl } from '@strudel.cycles/core'; -import { flashField, flash } from './flash.mjs'; -import { highlightExtension, highlightMiniLocations, updateMiniLocations } from './highlight.mjs'; -import { oneDark } from './themes/one-dark'; +import { isFlashEnabled, flash } from './flash.mjs'; +import { highlightMiniLocations, updateMiniLocations, isPatternHighlightingEnabled } from './highlight.mjs'; +import { theme } from './themes.mjs'; +import { isAutoCompletionEnabled } from './Autocomplete'; +import { keybindings } from './keybindings.mjs'; -const themeComparment = new Compartment(); +const extensions = { + isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), + isLineNumbersDisplayed: (on) => (on ? lineNumbers() : lineNumbers({ formatNumber: () => '' })), + theme, + isAutoCompletionEnabled, + isPatternHighlightingEnabled, + isFlashEnabled, + keybindings, +}; +const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()])); // https://codemirror.net/docs/guide/ -export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) { +export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) { + const initialSettings = Object.keys(compartments).map((key) => + compartments[key].of(extensions[key](parseBooleans(settings[key]))), + ); + console.log('settings', settings); let state = EditorState.create({ doc: initialCode, extensions: [ - themeComparment.of(theme), + ...initialSettings, javascript(), - lineNumbers(), - highlightExtension, highlightActiveLineGutter(), syntaxHighlighting(defaultHighlightStyle), keymap.of(defaultKeymap), - flashField, EditorView.updateListener.of((v) => onChange(v)), keymap.of([ { @@ -64,8 +76,10 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, the export class StrudelMirror { constructor(options) { - const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, theme, ...replOptions } = options; + const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options; this.code = initialCode; + this.root = root; + this.miniLocations = []; this.drawer = new Drawer((haps, time) => { const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped); @@ -100,14 +114,16 @@ export class StrudelMirror { await prebaked; }, afterEval: (options) => { - updateMiniLocations(this.editor, options.meta?.miniLocations); + // remember for when highlighting is toggled on + this.miniLocations = options.meta?.miniLocations; + updateMiniLocations(this.editor, this.miniLocations); replOptions?.afterEval?.(options); this.drawer.invalidate(); }, }); this.editor = initEditor({ root, - theme, + settings, initialCode, onChange: (v) => { if (v.docChanged) { @@ -139,13 +155,58 @@ export class StrudelMirror { highlight(haps, time) { highlightMiniLocations(this.editor, time, haps); } - setTheme(theme) { + setFontSize(size) { + this.root.style.fontSize = size + 'px'; + } + setFontFamily(family) { + this.root.style.fontFamily = family; + } + reconfigureExtension(key, value) { + if (!extensions[key]) { + console.warn(`extension ${key} is not known`); + return; + } + value = parseBooleans(value); + const newValue = extensions[key](value, this); this.editor.dispatch({ - effects: themeComparment.reconfigure(theme), + effects: compartments[key].reconfigure(newValue), }); } + setLineWrappingEnabled(enabled) { + this.reconfigureExtension('isLineWrappingEnabled', enabled); + } + setLineNumbersDisplayed(enabled) { + this.reconfigureExtension('isLineNumbersDisplayed', enabled); + } + setTheme(theme) { + this.reconfigureExtension('theme', theme); + } + setAutocompletionEnabled(enabled) { + this.reconfigureExtension('isAutoCompletionEnabled', enabled); + } + updateSettings(settings) { + this.setFontSize(settings.fontSize); + this.setFontFamily(settings.fontFamily); + for (let key in extensions) { + this.reconfigureExtension(key, settings[key]); + } + } + changeSetting(key, value) { + if (extensions[key]) { + this.reconfigureExtension(key, value); + return; + } else if (key === 'fontFamily') { + this.setFontFamily(value); + } else if (key === 'fontSize') { + this.setFontSize(value); + } + } setCode(code) { const changes = { from: 0, to: this.editor.state.doc.length, insert: code }; this.editor.dispatch({ changes }); } } + +function parseBooleans(value) { + return { true: true, false: false }[value] ?? value; +} diff --git a/packages/codemirror/flash.mjs b/packages/codemirror/flash.mjs index 9bc5c5936..6b37038ff 100644 --- a/packages/codemirror/flash.mjs +++ b/packages/codemirror/flash.mjs @@ -33,3 +33,5 @@ export const flash = (view, ms = 200) => { view.dispatch({ effects: setFlash.of(false) }); }, ms); }; + +export const isFlashEnabled = (on) => (on ? flashField : []); diff --git a/packages/codemirror/highlight.mjs b/packages/codemirror/highlight.mjs index 317c5fdf1..79724f8f4 100644 --- a/packages/codemirror/highlight.mjs +++ b/packages/codemirror/highlight.mjs @@ -124,3 +124,12 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi }); export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights]; + +export const isPatternHighlightingEnabled = (on, config) => { + on && + config && + setTimeout(() => { + updateMiniLocations(config.editor, config.miniLocations); + }, 100); + return on ? highlightExtension : []; +}; diff --git a/packages/codemirror/keybindings.mjs b/packages/codemirror/keybindings.mjs new file mode 100644 index 000000000..636350e95 --- /dev/null +++ b/packages/codemirror/keybindings.mjs @@ -0,0 +1,29 @@ +import { Prec } from '@codemirror/state'; +import { keymap, ViewPlugin } from '@codemirror/view'; +import { emacs } from '@replit/codemirror-emacs'; +import { vim } from '@replit/codemirror-vim'; +import { vscodeKeymap } from '@replit/codemirror-vscode-keymap'; + +const vscodePlugin = ViewPlugin.fromClass( + class { + constructor() {} + }, + { + provide: () => { + return Prec.highest(keymap.of([...vscodeKeymap])); + }, + }, +); + +const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []); + +const keymaps = { + vim, + emacs, + vscode: vscodeExtension, +}; + +export function keybindings(name) { + const keymap = keymaps[name]; + return keymap ? keymap() : []; +} diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 4e443648e..8a9fbc67d 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -33,13 +33,20 @@ }, "homepage": "https://github.com/tidalcycles/strudel#readme", "dependencies": { + "@codemirror/autocomplete": "^6.6.0", "@codemirror/commands": "^6.2.4", "@codemirror/lang-javascript": "^6.1.7", "@codemirror/language": "^6.6.0", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.10.0", "@lezer/highlight": "^1.1.4", - "@strudel.cycles/core": "workspace:*" + "@replit/codemirror-emacs": "^6.0.1", + "@replit/codemirror-vim": "^6.0.14", + "@replit/codemirror-vscode-keymap": "^6.0.2", + "@strudel.cycles/core": "workspace:*", + "@uiw/codemirror-themes": "^4.19.16", + "@uiw/codemirror-themes-all": "^4.19.16", + "react-dom": "^18.2.0" }, "devDependencies": { "vite": "^4.3.3" diff --git a/packages/codemirror/themes.mjs b/packages/codemirror/themes.mjs new file mode 100644 index 000000000..1f520623b --- /dev/null +++ b/packages/codemirror/themes.mjs @@ -0,0 +1,484 @@ +import { + abcdef, + androidstudio, + atomone, + aura, + bespin, + darcula, + dracula, + duotoneDark, + eclipse, + githubDark, + gruvboxDark, + materialDark, + nord, + okaidia, + solarizedDark, + sublime, + tokyoNight, + tokyoNightStorm, + vscodeDark, + xcodeDark, + bbedit, + duotoneLight, + githubLight, + gruvboxLight, + materialLight, + noctisLilac, + solarizedLight, + tokyoNightDay, + xcodeLight, +} from '@uiw/codemirror-themes-all'; + +import strudelTheme from './themes/strudel-theme'; +import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen'; +import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen'; +import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen'; +import teletext, { settings as teletextSettings } from './themes/teletext'; +import algoboy, { settings as algoboySettings } from './themes/algoboy'; +import terminal, { settings as terminalSettings } from './themes/terminal'; + +export const themes = { + strudelTheme, + bluescreen, + blackscreen, + whitescreen, + teletext, + algoboy, + terminal, + abcdef, + androidstudio, + atomone, + aura, + bespin, + darcula, + dracula, + duotoneDark, + eclipse, + githubDark, + gruvboxDark, + materialDark, + nord, + okaidia, + solarizedDark, + sublime, + tokyoNight, + tokyoNightStorm, + vscodeDark, + xcodeDark, + bbedit, + duotoneLight, + githubLight, + gruvboxLight, + materialLight, + noctisLilac, + solarizedLight, + tokyoNightDay, + xcodeLight, +}; + +// lineBackground is background with 50% opacity, to make sure the selection below is visible + +export const settings = { + strudelTheme: { + background: '#222', + lineBackground: '#22222299', + foreground: '#fff', + // foreground: '#75baff', + caret: '#ffcc00', + selection: 'rgba(128, 203, 196, 0.5)', + selectionMatch: '#036dd626', + // lineHighlight: '#8a91991a', // original + lineHighlight: '#00000050', + gutterBackground: 'transparent', + // gutterForeground: '#8a919966', + gutterForeground: '#8a919966', + }, + bluescreen: bluescreenSettings, + blackscreen: blackscreenSettings, + whitescreen: whitescreenSettings, + teletext: teletextSettings, + algoboy: algoboySettings, + terminal: terminalSettings, + abcdef: { + background: '#0f0f0f', + lineBackground: '#0f0f0f99', + foreground: '#defdef', + caret: '#00FF00', + selection: '#515151', + selectionMatch: '#515151', + gutterBackground: '#555', + gutterForeground: '#FFFFFF', + lineHighlight: '#314151', + }, + androidstudio: { + background: '#282b2e', + lineBackground: '#282b2e99', + foreground: '#a9b7c6', + caret: '#00FF00', + selection: '#343739', + selectionMatch: '#343739', + lineHighlight: '#343739', + }, + atomone: { + background: '#272C35', + lineBackground: '#272C3599', + foreground: '#9d9b97', + caret: '#797977', + selection: '#ffffff30', + selectionMatch: '#2B323D', + gutterBackground: '#272C35', + gutterForeground: '#465063', + gutterBorder: 'transparent', + lineHighlight: '#2B323D', + }, + aura: { + background: '#21202e', + lineBackground: '#21202e99', + foreground: '#edecee', + caret: '#a277ff', + selection: '#3d375e7f', + selectionMatch: '#3d375e7f', + gutterBackground: '#21202e', + gutterForeground: '#edecee', + gutterBorder: 'transparent', + lineHighlight: '#a394f033', + }, + bbedit: { + light: true, + background: '#FFFFFF', + lineBackground: '#FFFFFF99', + foreground: '#000000', + caret: '#FBAC52', + selection: '#FFD420', + selectionMatch: '#FFD420', + gutterBackground: '#f5f5f5', + gutterForeground: '#4D4D4C', + gutterBorder: 'transparent', + lineHighlight: '#00000012', + }, + bespin: { + background: '#28211c', + lineBackground: '#28211c99', + foreground: '#9d9b97', + caret: '#797977', + selection: '#36312e', + selectionMatch: '#4f382b', + gutterBackground: '#28211c', + gutterForeground: '#666666', + lineHighlight: 'rgba(255, 255, 255, 0.1)', + }, + darcula: { + background: '#2B2B2B', + lineBackground: '#2B2B2B99', + foreground: '#f8f8f2', + caret: '#FFFFFF', + selection: 'rgba(255, 255, 255, 0.1)', + selectionMatch: 'rgba(255, 255, 255, 0.2)', + gutterBackground: 'rgba(255, 255, 255, 0.1)', + gutterForeground: '#999', + gutterBorder: 'transparent', + lineHighlight: 'rgba(255, 255, 255, 0.1)', + }, + dracula: { + background: '#282a36', + lineBackground: '#282a3699', + foreground: '#f8f8f2', + caret: '#f8f8f0', + selection: 'rgba(255, 255, 255, 0.1)', + selectionMatch: 'rgba(255, 255, 255, 0.2)', + gutterBackground: '#282a36', + gutterForeground: '#6D8A88', + gutterBorder: 'transparent', + lineHighlight: 'rgba(255, 255, 255, 0.1)', + }, + duotoneLight: { + light: true, + background: '#faf8f5', + lineBackground: '#faf8f599', + foreground: '#b29762', + caret: '#93abdc', + selection: '#e3dcce', + selectionMatch: '#e3dcce', + gutterBackground: '#faf8f5', + gutterForeground: '#cdc4b1', + gutterBorder: 'transparent', + lineHighlight: '#EFEFEF', + }, + duotoneDark: { + background: '#2a2734', + lineBackground: '#2a273499', + foreground: '#6c6783', + caret: '#ffad5c', + selection: 'rgba(255, 255, 255, 0.1)', + gutterBackground: '#2a2734', + gutterForeground: '#545167', + lineHighlight: '#36334280', + }, + eclipse: { + light: true, + background: '#fff', + lineBackground: '#ffffff99', + foreground: '#000', + caret: '#FFFFFF', + selection: '#d7d4f0', + selectionMatch: '#d7d4f0', + gutterBackground: '#f7f7f7', + gutterForeground: '#999', + lineHighlight: '#e8f2ff', + gutterBorder: 'transparent', + }, + githubLight: { + light: true, + background: '#fff', + lineBackground: '#ffffff99', + foreground: '#24292e', + selection: '#BBDFFF', + selectionMatch: '#BBDFFF', + gutterBackground: '#fff', + gutterForeground: '#6e7781', + }, + githubDark: { + background: '#0d1117', + lineBackground: '#0d111799', + foreground: '#c9d1d9', + caret: '#c9d1d9', + selection: '#003d73', + selectionMatch: '#003d73', + lineHighlight: '#36334280', + }, + gruvboxDark: { + background: '#282828', + lineBackground: '#28282899', + foreground: '#ebdbb2', + caret: '#ebdbb2', + selection: '#bdae93', + selectionMatch: '#bdae93', + lineHighlight: '#3c3836', + gutterBackground: '#282828', + gutterForeground: '#7c6f64', + }, + gruvboxLight: { + light: true, + background: '#fbf1c7', + lineBackground: '#fbf1c799', + foreground: '#3c3836', + caret: '#af3a03', + selection: '#ebdbb2', + selectionMatch: '#bdae93', + lineHighlight: '#ebdbb2', + gutterBackground: '#ebdbb2', + gutterForeground: '#665c54', + gutterBorder: 'transparent', + }, + materialDark: { + background: '#2e3235', + lineBackground: '#2e323599', + foreground: '#bdbdbd', + caret: '#a0a4ae', + selection: '#d7d4f0', + selectionMatch: '#d7d4f0', + gutterBackground: '#2e3235', + gutterForeground: '#999', + gutterActiveForeground: '#4f5b66', + lineHighlight: '#545b61', + }, + materialLight: { + light: true, + background: '#FAFAFA', + lineBackground: '#FAFAFA99', + foreground: '#90A4AE', + caret: '#272727', + selection: '#80CBC440', + selectionMatch: '#FAFAFA', + gutterBackground: '#FAFAFA', + gutterForeground: '#90A4AE', + gutterBorder: 'transparent', + lineHighlight: '#CCD7DA50', + }, + noctisLilac: { + light: true, + background: '#f2f1f8', + lineBackground: '#f2f1f899', + foreground: '#0c006b', + caret: '#5c49e9', + selection: '#d5d1f2', + selectionMatch: '#d5d1f2', + gutterBackground: '#f2f1f8', + gutterForeground: '#0c006b70', + lineHighlight: '#e1def3', + }, + nord: { + background: '#2e3440', + lineBackground: '#2e344099', + foreground: '#FFFFFF', + caret: '#FFFFFF', + selection: '#3b4252', + selectionMatch: '#e5e9f0', + gutterBackground: '#2e3440', + gutterForeground: '#4c566a', + gutterActiveForeground: '#d8dee9', + lineHighlight: '#4c566a', + }, + okaidia: { + background: '#272822', + lineBackground: '#27282299', + foreground: '#FFFFFF', + caret: '#FFFFFF', + selection: '#49483E', + selectionMatch: '#49483E', + gutterBackground: '#272822', + gutterForeground: '#FFFFFF70', + lineHighlight: '#00000059', + }, + solarizedLight: { + light: true, + background: '#fdf6e3', + lineBackground: '#fdf6e399', + foreground: '#657b83', + caret: '#586e75', + selection: '#dfd9c8', + selectionMatch: '#dfd9c8', + gutterBackground: '#00000010', + gutterForeground: '#657b83', + lineHighlight: '#dfd9c8', + }, + solarizedDark: { + background: '#002b36', + lineBackground: '#002b3699', + foreground: '#93a1a1', + caret: '#839496', + selection: '#173541', + selectionMatch: '#aafe661a', + gutterBackground: '#00252f', + gutterForeground: '#839496', + lineHighlight: '#173541', + }, + sublime: { + background: '#303841', + lineBackground: '#30384199', + foreground: '#FFFFFF', + caret: '#FBAC52', + selection: '#4C5964', + selectionMatch: '#3A546E', + gutterBackground: '#303841', + gutterForeground: '#FFFFFF70', + lineHighlight: '#00000059', + }, + tokyoNightDay: { + light: true, + background: '#e1e2e7', + lineBackground: '#e1e2e799', + foreground: '#3760bf', + caret: '#3760bf', + selection: '#99a7df', + selectionMatch: '#99a7df', + gutterBackground: '#e1e2e7', + gutterForeground: '#3760bf', + gutterBorder: 'transparent', + lineHighlight: '#5f5faf11', + }, + tokyoNightStorm: { + background: '#24283b', + lineBackground: '#24283b99', + foreground: '#7982a9', + caret: '#c0caf5', + selection: '#6f7bb630', + selectionMatch: '#1f2335', + gutterBackground: '#24283b', + gutterForeground: '#7982a9', + gutterBorder: 'transparent', + lineHighlight: '#292e42', + }, + tokyoNight: { + background: '#1a1b26', + lineBackground: '#1a1b2699', + foreground: '#787c99', + caret: '#c0caf5', + selection: '#515c7e40', + selectionMatch: '#16161e', + gutterBackground: '#1a1b26', + gutterForeground: '#787c99', + gutterBorder: 'transparent', + lineHighlight: '#1e202e', + }, + vscodeDark: { + background: '#1e1e1e', + lineBackground: '#1e1e1e99', + foreground: '#9cdcfe', + caret: '#c6c6c6', + selection: '#6199ff2f', + selectionMatch: '#72a1ff59', + lineHighlight: '#ffffff0f', + gutterBackground: '#1e1e1e', + gutterForeground: '#838383', + gutterActiveForeground: '#fff', + }, + xcodeLight: { + light: true, + background: '#fff', + lineBackground: '#ffffff99', + foreground: '#3D3D3D', + selection: '#BBDFFF', + selectionMatch: '#BBDFFF', + gutterBackground: '#fff', + gutterForeground: '#AFAFAF', + lineHighlight: '#EDF4FF', + }, + xcodeDark: { + background: '#292A30', + lineBackground: '#292A3099', + foreground: '#CECFD0', + caret: '#fff', + selection: '#727377', + selectionMatch: '#727377', + lineHighlight: '#2F3239', + }, +}; + +function getColors(str) { + const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g; + const colors = []; + + let match; + while ((match = colorRegex.exec(str)) !== null) { + const color = match[0]; + if (!colors.includes(color)) { + colors.push(color); + } + } + + return colors; +} + +// TODO: remove +export function themeColors(theme) { + return getColors(stringifySafe(theme)); +} + +function getCircularReplacer() { + const seen = new WeakSet(); + return (key, value) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) { + return; + } + seen.add(value); + } + return value; + }; +} + +function stringifySafe(json) { + return JSON.stringify(json, getCircularReplacer()); +} + +export function injectStyle(rule) { + const newStyle = document.createElement('style'); + document.head.appendChild(newStyle); + const styleSheet = newStyle.sheet; + const ruleIndex = styleSheet.insertRule(rule, 0); + return () => styleSheet.deleteRule(ruleIndex); +} + +export const theme = (theme) => themes[theme] || themes.strudelTheme; diff --git a/packages/codemirror/themes/algoboy.js b/packages/codemirror/themes/algoboy.js new file mode 100644 index 000000000..399370e19 --- /dev/null +++ b/packages/codemirror/themes/algoboy.js @@ -0,0 +1,41 @@ +import { tags as t } from '@lezer/highlight'; +import { createTheme } from '@uiw/codemirror-themes'; +export const settings = { + background: '#9bbc0f', + foreground: '#0f380f', // whats that? + caret: '#0f380f', + selection: '#306230', + selectionMatch: '#ffffff26', + lineHighlight: '#8bac0f', + lineBackground: '#9bbc0f50', + //lineBackground: 'transparent', + gutterBackground: 'transparent', + gutterForeground: '#0f380f', + light: true, + customStyle: '.cm-line { line-height: 1 }', +}; +export default createTheme({ + theme: 'light', + settings, + styles: [ + { tag: t.keyword, color: '#0f380f' }, + { tag: t.operator, color: '#0f380f' }, + { tag: t.special(t.variableName), color: '#0f380f' }, + { tag: t.typeName, color: '#0f380f' }, + { tag: t.atom, color: '#0f380f' }, + { tag: t.number, color: '#0f380f' }, + { tag: t.definition(t.variableName), color: '#0f380f' }, + { tag: t.string, color: '#0f380f' }, + { tag: t.special(t.string), color: '#0f380f' }, + { tag: t.comment, color: '#0f380f' }, + { tag: t.variableName, color: '#0f380f' }, + { tag: t.tagName, color: '#0f380f' }, + { tag: t.bracket, color: '#0f380f' }, + { tag: t.meta, color: '#0f380f' }, + { tag: t.attributeName, color: '#0f380f' }, + { tag: t.propertyName, color: '#0f380f' }, + { tag: t.className, color: '#0f380f' }, + { tag: t.invalid, color: '#0f380f' }, + { tag: [t.unit, t.punctuation], color: '#0f380f' }, + ], +}); diff --git a/packages/codemirror/themes/blackscreen.js b/packages/codemirror/themes/blackscreen.js new file mode 100644 index 000000000..135285a37 --- /dev/null +++ b/packages/codemirror/themes/blackscreen.js @@ -0,0 +1,38 @@ +import { tags as t } from '@lezer/highlight'; +import { createTheme } from '@uiw/codemirror-themes'; +export const settings = { + background: 'black', + foreground: 'white', // whats that? + caret: 'white', + selection: '#ffffff20', + selectionMatch: '#036dd626', + lineHighlight: '#ffffff10', + lineBackground: '#00000050', + gutterBackground: 'transparent', + gutterForeground: '#8a919966', +}; +export default createTheme({ + theme: 'dark', + settings, + styles: [ + { tag: t.keyword, color: 'white' }, + { tag: t.operator, color: 'white' }, + { tag: t.special(t.variableName), color: 'white' }, + { tag: t.typeName, color: 'white' }, + { tag: t.atom, color: 'white' }, + { tag: t.number, color: 'white' }, + { tag: t.definition(t.variableName), color: 'white' }, + { tag: t.string, color: 'white' }, + { tag: t.special(t.string), color: 'white' }, + { tag: t.comment, color: 'white' }, + { tag: t.variableName, color: 'white' }, + { tag: t.tagName, color: 'white' }, + { tag: t.bracket, color: 'white' }, + { tag: t.meta, color: 'white' }, + { tag: t.attributeName, color: 'white' }, + { tag: t.propertyName, color: 'white' }, + { tag: t.className, color: 'white' }, + { tag: t.invalid, color: 'white' }, + { tag: [t.unit, t.punctuation], color: 'white' }, + ], +}); diff --git a/packages/codemirror/themes/bluescreen.js b/packages/codemirror/themes/bluescreen.js new file mode 100644 index 000000000..aa6489d6f --- /dev/null +++ b/packages/codemirror/themes/bluescreen.js @@ -0,0 +1,41 @@ +import { tags as t } from '@lezer/highlight'; +import { createTheme } from '@uiw/codemirror-themes'; +export const settings = { + background: '#051DB5', + lineBackground: '#051DB550', + foreground: 'white', // whats that? + caret: 'white', + selection: 'rgba(128, 203, 196, 0.5)', + selectionMatch: '#036dd626', + // lineHighlight: '#8a91991a', // original + lineHighlight: '#00000050', + gutterBackground: 'transparent', + // gutterForeground: '#8a919966', + gutterForeground: '#8a919966', +}; + +export default createTheme({ + theme: 'dark', + settings, + styles: [ + { tag: t.keyword, color: 'white' }, + { tag: t.operator, color: 'white' }, + { tag: t.special(t.variableName), color: 'white' }, + { tag: t.typeName, color: 'white' }, + { tag: t.atom, color: 'white' }, + { tag: t.number, color: 'white' }, + { tag: t.definition(t.variableName), color: 'white' }, + { tag: t.string, color: 'white' }, + { tag: t.special(t.string), color: 'white' }, + { tag: t.comment, color: 'white' }, + { tag: t.variableName, color: 'white' }, + { tag: t.tagName, color: 'white' }, + { tag: t.bracket, color: 'white' }, + { tag: t.meta, color: 'white' }, + { tag: t.attributeName, color: 'white' }, + { tag: t.propertyName, color: 'white' }, + { tag: t.className, color: 'white' }, + { tag: t.invalid, color: 'white' }, + { tag: [t.unit, t.punctuation], color: 'white' }, + ], +}); diff --git a/packages/codemirror/themes/one-dark.mjs b/packages/codemirror/themes/one-dark.mjs deleted file mode 100644 index cce836994..000000000 --- a/packages/codemirror/themes/one-dark.mjs +++ /dev/null @@ -1,139 +0,0 @@ -import { EditorView } from '@codemirror/view'; -import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'; -import { tags as t } from '@lezer/highlight'; - -// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors - -const chalky = '#e5c07b', - coral = '#e06c75', - cyan = '#56b6c2', - invalid = '#ffffff', - ivory = '#abb2bf', - stone = '#7d8799', // Brightened compared to original to increase contrast - malibu = '#61afef', - sage = '#98c379', - whiskey = '#d19a66', - violet = '#c678dd', - darkBackground = '#21252b', - highlightBackground = '#2c313a', - background = '#282c34', - tooltipBackground = '#353a42', - selection = '#3E4451', - cursor = '#528bff'; - -/// The colors used in the theme, as CSS color strings. -export const color = { - chalky, - coral, - cyan, - invalid, - ivory, - stone, - malibu, - sage, - whiskey, - violet, - darkBackground, - highlightBackground, - background, - tooltipBackground, - selection, - cursor, -}; - -/// The editor theme styles for One Dark. -export const oneDarkTheme = EditorView.theme( - { - '&': { - color: ivory, - backgroundColor: background, - }, - - '.cm-content': { - caretColor: cursor, - }, - - '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor }, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': - { backgroundColor: selection }, - - '.cm-panels': { backgroundColor: darkBackground, color: ivory }, - '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' }, - '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' }, - - '.cm-searchMatch': { - backgroundColor: '#72a1ff59', - outline: '1px solid #457dff', - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: '#6199ff2f', - }, - - '.cm-activeLine': { backgroundColor: '#6699ff0b' }, - '.cm-selectionMatch': { backgroundColor: '#aafe661a' }, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: '#bad0f847', - }, - - '.cm-gutters': { - backgroundColor: background, - color: stone, - border: 'none', - }, - - '.cm-activeLineGutter': { - backgroundColor: highlightBackground, - }, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: '#ddd', - }, - - '.cm-tooltip': { - border: 'none', - backgroundColor: tooltipBackground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: tooltipBackground, - borderBottomColor: tooltipBackground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - backgroundColor: highlightBackground, - color: ivory, - }, - }, - }, - { dark: true }, -); - -/// The highlighting style for code in the One Dark theme. -export const oneDarkHighlightStyle = HighlightStyle.define([ - { tag: t.keyword, color: violet }, - { tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral }, - { tag: [t.function(t.variableName), t.labelName], color: malibu }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey }, - { tag: [t.definition(t.name), t.separator], color: ivory }, - { tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky }, - { tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan }, - { tag: [t.meta, t.comment], color: stone }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.link, color: stone, textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: coral }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey }, - { tag: [t.processingInstruction, t.string, t.inserted], color: sage }, - { tag: t.invalid, color: invalid }, -]); - -/// Extension to enable the One Dark theme (both the editor theme and -/// the highlight style). -export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)]; diff --git a/packages/codemirror/themes/strudel-theme.js b/packages/codemirror/themes/strudel-theme.js new file mode 100644 index 000000000..4ae310609 --- /dev/null +++ b/packages/codemirror/themes/strudel-theme.js @@ -0,0 +1,45 @@ +import { tags as t } from '@lezer/highlight'; +import { createTheme } from '@uiw/codemirror-themes'; +export default createTheme({ + theme: 'dark', + settings: { + background: '#222', + foreground: '#75baff', // whats that? + caret: '#ffcc00', + selection: 'rgba(128, 203, 196, 0.5)', + selectionMatch: '#036dd626', + // lineHighlight: '#8a91991a', // original + lineHighlight: '#00000050', + gutterBackground: 'transparent', + // gutterForeground: '#8a919966', + gutterForeground: '#8a919966', + }, + styles: [ + { tag: t.keyword, color: '#c792ea' }, + { tag: t.operator, color: '#89ddff' }, + { tag: t.special(t.variableName), color: '#eeffff' }, + // { tag: t.typeName, color: '#f07178' }, // original + { tag: t.typeName, color: '#c3e88d' }, + { tag: t.atom, color: '#f78c6c' }, + // { tag: t.number, color: '#ff5370' }, // original + { tag: t.number, color: '#c3e88d' }, + { tag: t.definition(t.variableName), color: '#82aaff' }, + { tag: t.string, color: '#c3e88d' }, + // { tag: t.special(t.string), color: '#f07178' }, // original + { tag: t.special(t.string), color: '#c3e88d' }, + { tag: t.comment, color: '#7d8799' }, + // { tag: t.variableName, color: '#f07178' }, // original + { tag: t.variableName, color: '#c792ea' }, + // { tag: t.tagName, color: '#ff5370' }, // original + { tag: t.tagName, color: '#c3e88d' }, + { tag: t.bracket, color: '#525154' }, + // { tag: t.bracket, color: '#a2a1a4' }, // original + { tag: t.meta, color: '#ffcb6b' }, + { tag: t.attributeName, color: '#c792ea' }, + { tag: t.propertyName, color: '#c792ea' }, + + { tag: t.className, color: '#decb6b' }, + { tag: t.invalid, color: '#ffffff' }, + { tag: [t.unit, t.punctuation], color: '#82aaff' }, + ], +}); diff --git a/packages/codemirror/themes/teletext.js b/packages/codemirror/themes/teletext.js new file mode 100644 index 000000000..5fd9a5570 --- /dev/null +++ b/packages/codemirror/themes/teletext.js @@ -0,0 +1,50 @@ +import { tags as t } from '@lezer/highlight'; +import { createTheme } from '@uiw/codemirror-themes'; + +let colorA = '#6edee4'; +//let colorB = 'magenta'; +let colorB = 'white'; +let colorC = 'red'; +let colorD = '#f8fc55'; + +export const settings = { + background: '#000000', + foreground: colorA, // whats that? + caret: colorC, + selection: colorD, + selectionMatch: colorA, + lineHighlight: '#6edee440', // panel bg + lineBackground: '#00000040', + gutterBackground: 'transparent', + gutterForeground: '#8a919966', + customStyle: '.cm-line { line-height: 1 }', +}; + +let punctuation = colorD; +let mini = colorB; + +export default createTheme({ + theme: 'dark', + settings, + styles: [ + { tag: t.keyword, color: colorA }, + { tag: t.operator, color: mini }, + { tag: t.special(t.variableName), color: colorA }, + { tag: t.typeName, color: colorA }, + { tag: t.atom, color: colorA }, + { tag: t.number, color: mini }, + { tag: t.definition(t.variableName), color: colorA }, + { tag: t.string, color: mini }, + { tag: t.special(t.string), color: mini }, + { tag: t.comment, color: punctuation }, + { tag: t.variableName, color: colorA }, + { tag: t.tagName, color: colorA }, + { tag: t.bracket, color: punctuation }, + { tag: t.meta, color: colorA }, + { tag: t.attributeName, color: colorA }, + { tag: t.propertyName, color: colorA }, // methods + { tag: t.className, color: colorA }, + { tag: t.invalid, color: colorC }, + { tag: [t.unit, t.punctuation], color: punctuation }, + ], +}); diff --git a/packages/codemirror/themes/terminal.js b/packages/codemirror/themes/terminal.js new file mode 100644 index 000000000..1374bb867 --- /dev/null +++ b/packages/codemirror/themes/terminal.js @@ -0,0 +1,36 @@ +import { tags as t } from '@lezer/highlight'; +import { createTheme } from '@uiw/codemirror-themes'; +export const settings = { + background: 'black', + foreground: '#41FF00', // whats that? + caret: '#41FF00', + selection: '#ffffff20', + selectionMatch: '#036dd626', + lineHighlight: '#ffffff10', + gutterBackground: 'transparent', + gutterForeground: '#8a919966', +}; +export default createTheme({ + theme: 'dark', + settings, + styles: [ + { tag: t.keyword, color: '#41FF00' }, + { tag: t.operator, color: '#41FF00' }, + { tag: t.special(t.variableName), color: '#41FF00' }, + { tag: t.typeName, color: '#41FF00' }, + { tag: t.atom, color: '#41FF00' }, + { tag: t.number, color: '#41FF00' }, + { tag: t.definition(t.variableName), color: '#41FF00' }, + { tag: t.string, color: '#41FF00' }, + { tag: t.special(t.string), color: '#41FF00' }, + { tag: t.comment, color: '#41FF00' }, + { tag: t.variableName, color: '#41FF00' }, + { tag: t.tagName, color: '#41FF00' }, + { tag: t.bracket, color: '#41FF00' }, + { tag: t.meta, color: '#41FF00' }, + { tag: t.attributeName, color: '#41FF00' }, + { tag: t.propertyName, color: '#41FF00' }, + { tag: t.className, color: '#41FF00' }, + { tag: t.invalid, color: '#41FF00' }, + ], +}); diff --git a/packages/codemirror/themes/whitescreen.js b/packages/codemirror/themes/whitescreen.js new file mode 100644 index 000000000..22abad9e9 --- /dev/null +++ b/packages/codemirror/themes/whitescreen.js @@ -0,0 +1,38 @@ +import { tags as t } from '@lezer/highlight'; +import { createTheme } from '@uiw/codemirror-themes'; +export const settings = { + background: 'white', + foreground: 'black', // whats that? + caret: 'black', + selection: 'rgba(128, 203, 196, 0.5)', + selectionMatch: '#ffffff26', + lineHighlight: '#cccccc50', + lineBackground: '#ffffff50', + gutterBackground: 'transparent', + gutterForeground: 'black', + light: true, +}; +export default createTheme({ + theme: 'light', + settings, + styles: [ + { tag: t.keyword, color: 'black' }, + { tag: t.operator, color: 'black' }, + { tag: t.special(t.variableName), color: 'black' }, + { tag: t.typeName, color: 'black' }, + { tag: t.atom, color: 'black' }, + { tag: t.number, color: 'black' }, + { tag: t.definition(t.variableName), color: 'black' }, + { tag: t.string, color: 'black' }, + { tag: t.special(t.string), color: 'black' }, + { tag: t.comment, color: 'black' }, + { tag: t.variableName, color: 'black' }, + { tag: t.tagName, color: 'black' }, + { tag: t.bracket, color: 'black' }, + { tag: t.meta, color: 'black' }, + { tag: t.attributeName, color: 'black' }, + { tag: t.propertyName, color: 'black' }, + { tag: t.className, color: 'black' }, + { tag: t.invalid, color: 'black' }, + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e436542cd..60a91f14b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: packages/codemirror: dependencies: + '@codemirror/autocomplete': + specifier: ^6.6.0 + version: 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2) '@codemirror/commands': specifier: ^6.2.4 version: 6.2.4 @@ -89,9 +92,27 @@ importers: '@lezer/highlight': specifier: ^1.1.4 version: 1.1.4 + '@replit/codemirror-emacs': + specifier: ^6.0.1 + version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0) + '@replit/codemirror-vim': + specifier: ^6.0.14 + version: 6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0) + '@replit/codemirror-vscode-keymap': + specifier: ^6.0.2 + version: 6.0.2(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0) '@strudel.cycles/core': specifier: workspace:* version: link:../core + '@uiw/codemirror-themes': + specifier: ^4.19.16 + version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0) + '@uiw/codemirror-themes-all': + specifier: ^4.19.16 + version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0) + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) devDependencies: vite: specifier: ^4.3.3 diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index 9fb783a49..8423315fe 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -385,6 +385,8 @@ function SettingsTab({ scheduler }) { keybindings, isLineNumbersDisplayed, isAutoCompletionEnabled, + isFlashEnabled, + isPatternHighlightingEnabled, isLineWrappingEnabled, fontSize, fontFamily, @@ -462,6 +464,16 @@ function SettingsTab({ scheduler }) { onChange={(cbEvent) => settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)} value={isLineWrappingEnabled} /> + settingsMap.setKey('isPatternHighlightingEnabled', cbEvent.target.checked)} + value={isPatternHighlightingEnabled} + /> + settingsMap.setKey('isFlashEnabled', cbEvent.target.checked)} + value={isFlashEnabled} + /> Try clicking the logo in the top left! diff --git a/website/src/repl/vanillarepl.mjs b/website/src/repl/vanillarepl.mjs index 42c5ebc08..ac5708d70 100644 --- a/website/src/repl/vanillarepl.mjs +++ b/website/src/repl/vanillarepl.mjs @@ -3,24 +3,53 @@ import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; import { prebake } from './prebake.mjs'; import { settingsMap } from '@src/settings.mjs'; -import { themes } from '@src/repl/themes.mjs'; import { setLatestCode } from '../settings.mjs'; import { hash2code, code2hash } from './helpers.mjs'; import { createClient } from '@supabase/supabase-js'; import * as tunes from './tunes.mjs'; -const onEvent = (key, callback) => { - const listener = (e) => { - if (e.data === key) { - callback(); - } - }; - window.addEventListener('message', listener); - return () => window.removeEventListener('message', listener); -}; +const supabase = createClient( + 'https://pidxdsxphlhzjnzmifth.supabase.co', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', +); -function run() { - const { latestCode } = settingsMap.get(); +async function initCodeFromUrl() { + // load code from url hash (either short hash from database or decode long hash) + try { + const initialUrl = window.location.href; + const hash = initialUrl.split('?')[1]?.split('#')?.[0]; + const codeParam = window.location.href.split('#')[1] || ''; + // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length) + if (codeParam) { + // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) + return hash2code(codeParam); + } else if (hash) { + return supabase + .from('code') + .select('code') + .eq('hash', hash) + .then(({ data, error }) => { + if (error) { + console.warn('failed to load hash', error); + } + if (data.length) { + //console.log('load hash from database', hash); + return data[0].code; + } + }); + } + } catch (err) { + console.warn('failed to decode', err); + } +} +const initialCode = initCodeFromUrl(); + +async function run() { + const container = document.getElementById('code'); + if (!container) { + console.warn('could not init: no container found'); + return; + } /* let clearCanvas; if (typeof window !== 'undefined') { @@ -29,54 +58,18 @@ if (typeof window !== 'undefined') { } */ // Create a single supabase client for interacting with your database - const supabase = createClient( - 'https://pidxdsxphlhzjnzmifth.supabase.co', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', - ); - async function initCode() { - // load code from url hash (either short hash from database or decode long hash) - try { - const initialUrl = window.location.href; - const hash = initialUrl.split('?')[1]?.split('#')?.[0]; - const codeParam = window.location.href.split('#')[1] || ''; - // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length) - if (codeParam) { - // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) - return hash2code(codeParam); - } else if (hash) { - return supabase - .from('code') - .select('code') - .eq('hash', hash) - .then(({ data, error }) => { - if (error) { - console.warn('failed to load hash', error); - } - if (data.length) { - //console.log('load hash from database', hash); - return data[0].code; - } - }); - } - } catch (err) { - console.warn('failed to decode', err); - } - } - const container = document.getElementById('code'); - const setFontSize = (size) => (container.style.fontSize = size + 'px'); - const setFontFamily = (family) => (container.style.fontFamily = family); + const settings = settingsMap.get(); /* const drawContext = getDrawContext() const drawTime = [-2, 2]; */ - console.log('container', container); const editor = new StrudelMirror({ - theme: themes.strudelTheme, defaultOutput: webaudioOutput, getTime: () => getAudioContext().currentTime, transpiler, root: container, initialCode: '// LOADING', + settings, /* drawTime, onDraw: (haps, time) => drawPianoroll({ haps, time, ctx: drawContext, drawTime, fold: 1 }), */ @@ -87,56 +80,27 @@ const drawTime = [-2, 2]; */ }, }); - function getRandomTune() { - const allTunes = Object.entries(tunes); - const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; - const [name, code] = randomItem(allTunes); - return { name, code }; - } - const { code: randomTune, name } = getRandomTune(); - function init() { - if (!container) { - console.warn('could not init: no container found'); - return; - } - const settings = settingsMap.get(); - setFontSize(settings.fontSize); - setFontFamily(settings.fontFamily); - initCode().then((decoded) => { - let msg; - if (decoded) { - editor.setCode(decoded); - msg = `I have loaded the code from the URL.`; - } else if (latestCode) { - editor.setCode(latestCode); - msg = `Your last session has been loaded!`; - } /* if(randomTune) */ else { - editor.setCode(randomTune); - msg = `A random code snippet named "${name}" has been loaded!`; - } - console.log('msg', msg); - /* logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); - setPending(false); */ - }); - - editor.setTheme(themes[settings.theme || 'strudelTheme']); + // init settings + editor.updateSettings(settings); + const decoded = await initialCode; + let msg; + if (decoded) { + editor.setCode(decoded); + msg = `I have loaded the code from the URL.`; + } else if (settings.latestCode) { + editor.setCode(settings.latestCode); + msg = `Your last session has been loaded!`; + } /* if(randomTune) */ else { + editor.setCode(randomTune); + msg = `A random code snippet named "${name}" has been loaded!`; } - init(); + console.log('msg', msg); + /* logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); + setPending(false); */ - settingsMap.listen((settings, key) => { - const value = settings[key]; - if (key === 'theme') { - editor.setTheme(themes[value]); - } else if (key === 'fontFamily') { - // console.log('change fontFamily', value); - setFontFamily(value); - } else if (key === 'fontSize') { - // console.log('change fontSize', value); - setFontSize(value); - } - }); + settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key])); onEvent('strudel-toggle-play', () => editor.toggle()); @@ -150,3 +114,20 @@ onEvent('strudel-container', () => { run(); } }); + +function onEvent(key, callback) { + const listener = (e) => { + if (e.data === key) { + callback(); + } + }; + window.addEventListener('message', listener); + return () => window.removeEventListener('message', listener); +} + +function getRandomTune() { + const allTunes = Object.entries(tunes); + const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; + const [name, code] = randomItem(allTunes); + return { name, code }; +} diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 5608b8943..2de6e4d79 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -7,6 +7,8 @@ export const defaultSettings = { keybindings: 'codemirror', isLineNumbersDisplayed: true, isAutoCompletionEnabled: false, + isPatternHighlightingEnabled: true, + isFlashEnabled: true, isLineWrappingEnabled: false, theme: 'strudelTheme', fontFamily: 'monospace', @@ -27,6 +29,8 @@ export function useSettings() { isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false, isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false, isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false, + isPatternHighlightingEnabled: [true, 'true'].includes(state.isPatternHighlightingEnabled) ? true : false, + isFlashEnabled: [true, 'true'].includes(state.isFlashEnabled) ? true : false, fontSize: Number(state.fontSize), panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom', }; diff --git a/website/tsconfig.json b/website/tsconfig.json index 90aa524fd..ed79dc036 100644 --- a/website/tsconfig.json +++ b/website/tsconfig.json @@ -12,6 +12,7 @@ "paths": { "@components/*": ["src/components/*"], "@src/*": ["src/*"], + "~/*": ["/*"], } } } \ No newline at end of file