This commit is contained in:
2023-12-16 20:19:52 +02:00
24 changed files with 10632 additions and 521 deletions

View File

@ -1,6 +1,7 @@
import { EditorView } from "@codemirror/view";
import { sendToServer, type OSCMessage, oscMessages } from "./IO/OSC";
import { getAllScaleNotes, nearScales, seededRandom } from "zifferjs";
import colorschemes from "./colors.json";
import {
MidiCCEvent,
MidiConnection,
@ -87,8 +88,7 @@ export class UserAPI {
public randomGen = Math.random;
public currentSeed: string | undefined = undefined;
public localSeeds = new Map<string, Function>();
public patternCache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5 });
public tempCache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5 });
public patternCache = new LRUCache({ max: 10000, ttl: 10000 * 60 * 5 });
public invalidPatterns: {[key: string]: boolean} = {};
public cueTimes: { [key: string]: number } = {};
private errorTimeoutID: number = 0;
@ -147,8 +147,8 @@ export class UserAPI {
? code
: (this.app.selectedExample as string);
}
this.clearPatternCache();
this.stop();
this.resetAllFromCache();
this.play();
};
@ -159,6 +159,7 @@ export class UserAPI {
current_universe.example.candidate! = "";
current_universe.example.committed! = "";
}
this.clearPatternCache();
this.stop();
};
@ -168,10 +169,10 @@ export class UserAPI {
current_universe.example.candidate! = "";
current_universe.example.committed! = "";
}
this.clearPatternCache();
this.stop();
this.play();
this.app.exampleIsPlaying = true;
this.resetAllFromCache();
evaluateOnce(this.app, code as string);
};
@ -220,7 +221,7 @@ export class UserAPI {
clearTimeout(this.printTimeoutID);
clearTimeout(this.errorTimeoutID);
this.app.interface.error_line.innerHTML = message as string;
this.app.interface.error_line.style.color = "white";
this.app.interface.error_line.style.color = "red";
this.app.interface.error_line.classList.remove("hidden");
// @ts-ignore
this.printTimeoutID = setTimeout(
@ -724,7 +725,7 @@ export class UserAPI {
maybeToNumber = (something: any): number|any => {
// If something is BigInt
if(something && typeof something === "bigint") {
if(typeof something === "bigint") {
return Number(something);
} else {
return something;
@ -743,7 +744,7 @@ export class UserAPI {
if(isGenerator(value)) {
if(this.patternCache.has(key)) {
const cachedValue = (this.patternCache.get(key) as Generator<any>).next().value
if(!cachedValue) {
if(cachedValue!==0 && !cachedValue) {
const generator = value as unknown as Generator<any>
this.patternCache.set(key, generator);
return this.maybeToNumber(generator.next().value);
@ -757,7 +758,7 @@ export class UserAPI {
} else if(isGeneratorFunction(value)) {
if(this.patternCache.has(key)) {
const cachedValue = (this.patternCache.get(key) as Generator<any>).next().value;
if(cachedValue) {
if(cachedValue || cachedValue===0 || cachedValue===0n) {
return this.maybeToNumber(cachedValue);
} else {
const generator = value();
@ -2416,4 +2417,34 @@ export class UserAPI {
this.cueTimes[functionName] = this.app.clock.pulses_since_origin;
};
public theme = (color_scheme: string): void => {
this.app.readTheme(color_scheme);
console.log("Changing color scheme for: ", color_scheme)
}
public themeName = (): string => {
return this.app.currentThemeName;
}
public randomTheme = (): void => {
let theme_names = this.getThemes();
let selected_theme = theme_names[Math.floor(Math.random() * theme_names.length)];
this.app.readTheme(selected_theme);
this.app.api.log(selected_theme);
}
public nextTheme = (): void => {
let theme_names = this.getThemes();
let current_theme = this.app.api.themeName();
let current_theme_idx = theme_names.indexOf(current_theme);
let next_theme_idx = (current_theme_idx + 1) % theme_names.length;
let next_theme = theme_names[next_theme_idx];
this.app.readTheme(next_theme);
this.app.api.log(next_theme);
}
public getThemes = (): string[] => {
return Object.keys(colorschemes);
}
}

View File

@ -27,6 +27,7 @@ import { midi } from "./documentation/learning/midi";
import { osc } from "./documentation/learning/osc";
import { patterns } from "./documentation/patterns/patterns";
import { functions } from "./documentation/patterns/functions";
import { generators } from "./documentation/patterns/generators";
import { variables } from "./documentation/patterns/variables";
import { probabilities } from "./documentation/patterns/probabilities";
import { lfos } from "./documentation/patterns/lfos";
@ -36,18 +37,17 @@ import { ziffers_rhythm } from "./documentation/patterns/ziffers/ziffers_rhythm"
import { ziffers_algorithmic } from "./documentation/patterns/ziffers/ziffers_algorithmic";
import { ziffers_tonnetz } from "./documentation/patterns/ziffers/ziffers_tonnetz";
import { ziffers_syncing } from "./documentation/patterns/ziffers/ziffers_syncing";
import { synths } from "./documentation/learning/audio_engine/synths";
// Setting up the Markdown converter with syntax highlighting
import showdown from "showdown";
import showdownHighlight from "showdown-highlight";
import "highlight.js/styles/atom-one-dark-reasonable.min.css";
import { createDocumentationStyle } from "./DomElements";
import { filters } from "./documentation/learning/audio_engine/filters";
showdown.setFlavor("github");
export const key_shortcut = (shortcut: string): string => {
return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">${shortcut}</kbd>`;
return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-brightwhite bg-brightblack border border-black rounded-lg">${shortcut}</kbd>`;
};
export const makeExampleFactory = (application: Editor): Function => {
@ -63,13 +63,11 @@ export const makeExampleFactory = (application: Editor): Function => {
return `
<details ${open ? "open" : ""}>
<summary >${description}
<button class="ml-4 py-1 align-top text-base px-4 hover:bg-green-700 bg-emerald-600 inline-block" onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])">▶️ Play</button>
<button class="py-1 text-base px-4 hover:bg-neutral-600 bg-neutral-500 inline-block " onclick="app.api._stopDocExample()">&#x23f8;&#xFE0F; Pause</button>
<button class="py-1 text-base px-4 hover:bg-neutral-900 bg-neutral-800 inline-block " onclick="navigator.clipboard.writeText(app.api.codeExamples['${codeId}'])">📎 Copy</button>
<button class="ml-4 py-1 align-top text-base px-4 hover:bg-brightgreen bg-green inline-block text-selection_foreground" onclick="app.api._playDocExample(app.api.codeExamples['${codeId}'])">▶️ Play</button>
<button class="py-1 text-base px-4 hover:brightyellow bg-yellow text-selection_foreground inline-block" onclick="app.api._stopDocExample()">&#x23f8;&#xFE0F; Pause</button>
<button class="py-1 text-base px-4 hover:bg-brightmagenta bg-magenta text-selection_foreground inline-block" onclick="navigator.clipboard.writeText(app.api.codeExamples['${codeId}'])">📎 Copy</button>
</summary>
\`\`\`javascript
${code}
\`\`\`
<pre><code class="hljs language-javascript">${code.trim()}</code></pre>
</details>
`;
};
@ -109,6 +107,7 @@ export const documentation_factory = (application: Editor) => {
variables: variables(application),
probabilities: probabilities(application),
functions: functions(application),
generators: generators(application),
shortcuts: shortcuts(application),
amplitude: amplitude(application),
effects: effects(application),
@ -170,12 +169,15 @@ export const updateDocumentationContent = (app: Editor, bindings: any) => {
* @param app - The editor application.
* @param bindings - Additional bindings for the showdown converter.
*/
let loading_message: string = "<h1 class='border-4 py-2 px-2 mx-48 mt-48 text-center text-2xl text-white'>Loading! <b class='text-red'>Clic to refresh!</b></h1>";
let loading_message: string = "<h1 class='border-4 py-2 px-2 mx-48 mt-48 text-center text-2xl text-brightwhite'>Loading! <b class='text-red'>Clic to refresh!</b></h1>";
const converter = new showdown.Converter({
emoji: true,
moreStyling: true,
backslashEscapesHTMLTags: true,
extensions: [showdownHighlight({ auto_detection: true }), ...bindings],
extensions: [showdownHighlight({
pre: true,
auto_detection: false
}), ...bindings],
});
console.log(app.currentDocumentationPane);

View File

@ -43,6 +43,8 @@ export const singleElements = {
midi_clock_checkbox: "send-midi-clock",
midi_channels_scripts: "midi-channels-scripts",
midi_clock_ppqn: "midi-clock-ppqn-input",
theme_selector: "theme-selector",
theme_previewer: "theme-previewer",
load_demo_songs: "load-demo-songs",
normal_mode_button: "normal-mode",
vim_mode_button: "vim-mode",
@ -71,30 +73,30 @@ export const createDocumentationStyle = (app: Editor) => {
*/
return {
h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-4 pt-4 pb-3 px-2",
h2: "text-white lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-2 pt-12 pb-3 px-2",
h3: "text-white lg:text-2xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 border-l-2 border-b-2 lg:mb-4 mb-4 pb-2 px-2 lg:mt-16",
h1: "text-brightwhite lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-4 pt-4 pb-3 px-2",
h2: "text-brightwhite lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-4 mb-4 border-b-2 pt-12 pb-3 px-2",
h3: "text-brightwhite lg:text-2xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 border-l-2 border-b-2 lg:mb-4 mb-4 pb-2 px-2 lg:mt-16",
ul: "text-underline ml-12",
li: "list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 my-2 leading-normal",
p: "lg:text-2xl text-base text-white lg:mx-6 mx-2 my-4 leading-normal",
warning:
"animate-pulse lg:text-2xl font-bold text-rose-600 lg:mx-6 mx-2 my-4 leading-normal",
a: "lg:text-2xl text-base text-orange-300",
"animate-pulse lg:text-2xl font-bold text-brightred lg:mx-6 mx-2 my-4 leading-normal",
a: "lg:text-2xl text-base text-white",
code: `lg:my-4 sm:my-1 text-base lg:text-xl block whitespace-pre overflow-x-hidden`,
icode:
"lg:my-1 my-1 lg:text-xl sm:text-xs text-white font-mono bg-neutral-600",
ic: "lg:my-1 my-1 lg:text-xl sm:text-xs text-white font-mono bg-neutral-600",
blockquote: "text-neutral-200 border-l-4 border-neutral-500 pl-4 my-4 mx-4",
"lg:my-1 my-1 lg:text-xl sm:text-xs text-brightwhite font-mono bg-brightblack",
ic: "lg:my-1 my-1 lg:text-xl sm:text-xs text-brightwhite font-mono bg-brightblack",
blockquote: "text-brightwhite border-l-4 border-white pl-4 my-4 mx-4",
details:
"lg:mx-20 py-2 px-6 lg:text-2xl text-white border-l-8 box-border bg-neutral-900",
"lg:mx-20 py-2 px-6 lg:text-2xl text-white border-l-8 box-border bg-selection_foreground",
summary: "font-semibold text-xl",
table:
"justify-center lg:my-12 my-2 lg:mx-12 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse",
thead:
"text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400",
"text-xs text-gray-700 uppercase",
th: "",
td: "",
tr: "",
box: "border bg-red-500",
box: "border bg-red",
};
};

View File

@ -1,5 +1,6 @@
import { Prec } from "@codemirror/state";
import { indentWithTab } from "@codemirror/commands";
import { tags as t } from "@lezer/highlight";
import {
keymap,
lineNumbers,
@ -7,8 +8,6 @@ import {
drawSelection,
highlightActiveLine,
dropCursor,
// rectangularSelection,
// crosshairCursor,
highlightActiveLineGutter,
} from "@codemirror/view";
import { Extension, EditorState } from "@codemirror/state";
@ -18,6 +17,7 @@ import {
syntaxHighlighting,
indentOnInput,
bracketMatching,
HighlightStyle,
} from "@codemirror/language";
import { defaultKeymap, historyKeymap, history } from "@codemirror/commands";
import { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
@ -30,12 +30,215 @@ import { lintKeymap } from "@codemirror/lint";
import { Compartment } from "@codemirror/state";
import { Editor } from "./main";
import { EditorView } from "codemirror";
import { toposTheme } from "./themes/toposTheme";
import { javascript } from "@codemirror/lang-javascript";
import { inlineHoveringTips } from "./documentation/inlineHelp";
import { toposCompletions, soundCompletions } from "./documentation/inlineHelp";
import { javascriptLanguage } from "@codemirror/lang-javascript";
export const getCodeMirrorTheme = (theme: {[key: string]: string}): Extension => {
// @ts-ignore
const black = theme["black"],
red = theme["red"],
green = theme["green"],
yellow = theme["yellow"],
blue = theme["blue"],
magenta = theme["magenta"],
cyan = theme["cyan"],
white = theme["white"],
// @ts-ignore
brightblack = theme["brightblack"],
// @ts-ignore
brightred = theme["brightred"],
brightgreen = theme["brightgreen"],
// @ts-ignore
brightyellow = theme["brightyellow"],
// @ts-ignore
brightblue = theme["brightblue"],
// @ts-ignore
brightmagenta = theme["brightmagenta"],
// @ts-ignore
brightcyan = theme["brightcyan"],
brightwhite = theme["brightwhite"],
background = theme["background"],
selection_foreground = theme["selection_foreground"],
cursor = theme["cursor"],
foreground = theme["foreground"],
selection_background = theme["selection_background"];
const toposTheme = EditorView.theme( {
"&": {
color: background,
// backgroundColor: background,
backgroundColor: "transparent",
fontSize: "24px",
fontFamily: "IBM Plex Mono",
},
".cm-content": {
caretColor: cursor,
fontFamily: "IBM Plex Mono",
},
".cm-cursor, .cm-dropCursor": {
borderLeftColor: cursor,
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
{
backgroundColor: selection_foreground,
border: `0.5px solid ${selection_background}`,
},
".cm-panels": {
backgroundColor: selection_background,
color: red,
},
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
".cm-search.cm-panel": { backgroundColor: "transparent" },
".cm-searchMatch": {
outline: `1px solid ${magenta}`,
},
".cm-searchMatch.cm-searchMatch-selected": {
backgroundColor: red,
},
".cm-activeLine": {
// backgroundColor: highlightBackground
backgroundColor: `${selection_foreground}`,
},
".cm-selectionMatch": {
backgroundColor: yellow,
outline: `1px solid ${red}`,
},
"&.cm-focused .cm-matchingBracket": {
color: yellow,
// outline: `1px solid ${base02}`,
},
"&.cm-focused .cm-nonmatchingBracket": {
color: yellow,
},
".cm-gutters": {
//backgroundColor: base00,
backgroundColor: "transparent",
color: foreground,
},
".cm-activeLineGutter": {
backgroundColor: selection_background,
color: selection_foreground,
},
".cm-foldPlaceholder": {
border: "none",
color: `${blue}`,
},
".cm-tooltip": {
border: "none",
backgroundColor: background,
},
".cm-tooltip .cm-tooltip-arrow:before": {},
".cm-tooltip .cm-tooltip-arrow:after": {
borderTopColor: background,
borderBottomColor: background,
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: background,
color: background,
},
},
},
{ dark: true },
);
let toposHighlightStyle = HighlightStyle.define([
{ tag: t.paren, color: brightwhite },
{ tag: [t.propertyName, t.punctuation, t.variableName], color: brightwhite },
{ tag: t.keyword, color: yellow },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: red, },
{ tag: [t.function(t.variableName)], color: blue },
{ tag: [t.labelName], color: red },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: cyan, },
{ tag: [t.definition(t.name), t.separator], color: magenta },
{ tag: [t.brace], color: white },
{ tag: [t.annotation], color: blue, },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: yellow, },
{ tag: [t.typeName, t.className], color: magenta, },
{ tag: [t.operator, t.operatorKeyword], color: blue, },
{ tag: [t.tagName], color: blue, },
{ tag: [t.squareBracket], color: blue, },
{ tag: [t.angleBracket], color: blue, },
{ tag: [t.attributeName], color: red, },
{ tag: [t.regexp], color: brightgreen, },
{ tag: [t.quote], color: green, },
{ tag: [t.string], color: green },
{
tag: t.link,
color: green,
textDecoration: "underline",
textUnderlinePosition: "under",
},
{
tag: [t.url, t.escape, t.special(t.string)],
color: green,
},
{ tag: [t.meta], color: brightwhite },
{ tag: [t.comment], color: brightwhite, fontStyle: "italic" },
{ tag: t.monospace, color: brightwhite },
{ tag: t.strong, fontWeight: "bold", color: white },
{ tag: t.emphasis, fontStyle: "italic", color: white },
{ tag: t.strikethrough, textDecoration: "line-through" },
{ tag: t.heading, fontWeight: "bold", color: white },
{ tag: t.heading1, fontWeight: "bold", color: white },
{
tag: [t.heading2, t.heading3, t.heading4],
fontWeight: "bold",
color: yellow,
},
{
tag: [t.heading5, t.heading6],
color: red,
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: green},
{
tag: [t.processingInstruction, t.inserted],
color: green,
},
{
tag: [t.contentSeparator],
color: green,
},
{ tag: t.invalid, color: red, borderBottom: `1px dotted ${red}` },
]);
return [ toposTheme, syntaxHighlighting(toposHighlightStyle),
]
}
// const debugTheme = EditorView.theme({
// ".cm-line span": {
// position: "relative",
// },
// ".cm-line span:hover::after": {
// position: "absolute",
// bottom: "100%",
// left: 0,
// background: "black",
// color: "white",
// border: "solid 2px",
// borderRadius: "5px",
// content: "var(--tags)",
// width: `max-content`,
// padding: "1px 4px",
// zIndex: 10,
// pointerEvents: "none",
// },
// });
// const debugHighlightStyle = HighlightStyle.define(
// // @ts-ignore
// Object.entries(t).map(([key, value]) => {
// return { tag: value, "--tags": `"tag.${key}"` };
// })
// );
// const debug = [debugTheme, syntaxHighlighting(debugHighlightStyle)];
export const jsCompletions = javascriptLanguage.data.of({
autocomplete: toposCompletions,
});
@ -70,6 +273,7 @@ export const editorSetup: Extension = (() => [
export const installEditor = (app: Editor) => {
app.vimModeCompartment = new Compartment();
app.hoveringCompartment = new Compartment();
app.themeCompartment = new Compartment();
app.completionsCompartment = new Compartment();
app.withLineNumbers = new Compartment();
app.chosenLanguage = new Compartment();
@ -99,7 +303,10 @@ export const installEditor = (app: Editor) => {
app.settings.completions ? [jsCompletions, toposSoundCompletions] : [],
),
editorSetup,
toposTheme,
app.themeCompartment.of(
getCodeMirrorTheme(app.getColorScheme("Tomorrow Night Burns")),
// debug
),
app.chosenLanguage.of(javascript()),
];
app.dynamicPlugins = new Compartment();

View File

@ -136,7 +136,7 @@ export class AppSettings {
*/
public vimMode: boolean = false;
public theme: string = "toposTheme";
public theme: string = "Everblush";
public font: string = "IBM Plex Mono";
public font_size: number = 24;
public universes: Universes;

View File

@ -1,6 +1,7 @@
import { EditorView } from "codemirror";
import { vim } from "@replit/codemirror-vim";
import { type Editor } from "./main";
import colors from "./colors.json";
import {
documentation_factory,
hideDocumentation,
@ -57,9 +58,9 @@ export const installInterfaceLogic = (app: Editor) => {
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener("click", (event) => {
// Updating the CSS accordingly
tabs[i].classList.add("bg-orange-300");
tabs[i].classList.add("bg-foreground");
for (let j = 0; j < tabs.length; j++) {
if (j != i) tabs[j].classList.remove("bg-orange-300");
if (j != i) tabs[j].classList.remove("bg-foreground");
}
app.currentFile().candidate = app.view.state.doc.toString();
@ -289,6 +290,18 @@ export const installInterfaceLogic = (app: Editor) => {
});
});
app.interface.theme_selector.addEventListener("change", () => {
app.settings.theme = (app.interface.theme_selector as HTMLSelectElement).value;
app.readTheme(app.settings.theme);
// @ts-ignore
let selected_theme = colors[app.settings.theme as string];
let theme_preview = "";
for (const [key, _] of Object.entries(selected_theme)) {
theme_preview += `<p class="inline text-${key} bg-${key}">█</div>`;
}
app.interface.theme_previewer.innerHTML = theme_preview;
});
app.interface.settings_button.addEventListener("click", () => {
// Populate the font selector
const fontFamilySelect = document.getElementById(
@ -298,6 +311,26 @@ export const installInterfaceLogic = (app: Editor) => {
fontFamilySelect.value = app.settings.font;
}
app.interface.theme_selector.innerHTML = "";
let all_themes = Object.keys(colors);
all_themes.sort((a, b) => {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
app.interface.theme_selector.innerHTML = all_themes.map((color) => {
return `<option value="${color}">${color}</option>`
}).join("");
// Set the selected theme in the selector to app.settings.theme
// @ts-ignore
app.interface.theme_selector.value = app.settings.theme;
// @ts-ignore
let selected_theme = colors[app.settings.theme as string];
let theme_preview = "<div class='ml-6'>";
for (const [key, _] of Object.entries(selected_theme)) {
theme_preview += `<p class="inline text-${key} bg-${key}">█</p>`;
}
theme_preview += "</div>";
app.interface.theme_previewer.innerHTML = theme_preview;
// Populate the font family selector
const doughNudgeRange = app.interface.dough_nudge_range as HTMLInputElement;
doughNudgeRange.value = app.dough_nudge.toString();
@ -512,6 +545,7 @@ export const installInterfaceLogic = (app: Editor) => {
"midi",
"osc",
"functions",
"generators",
"lfos",
"probabilities",
"variables",

View File

@ -9,6 +9,7 @@ import {
import { SkipEvent } from "./SkipEvent";
import { SoundParams } from "./SoundEvent";
import { centsToSemitones, edoToSemitones, ratiosToSemitones } from "zifferjs/src/scale";
import { safeMod } from "zifferjs/src/utils";
export type EventOperation<T> = (instance: T, ...args: any[]) => void;
@ -210,9 +211,14 @@ export class AbstractEvent {
* @param func - The function to be applied to the Event
* @returns The transformed Event
*/
return this.modify(func);
return this.modify(func).update();
};
mod = (value: number): AbstractEvent => {
this.values.originalPitch = safeMod(this.values.originalPitch, value);
return this.update();
}
noteLength = (
value: number | number[],
...kwargs: number[]
@ -297,8 +303,7 @@ export abstract class AudibleEvent extends AbstractEvent {
this.values["pitch"] = value;
this.values["originalPitch"] = value;
this.defaultPitchKeyScale();
this.update();
return this;
return this.update();
};
pc = this.pitch;
@ -317,8 +322,10 @@ export abstract class AudibleEvent extends AbstractEvent {
this.values.key &&
(this.values.pitch || this.values.pitch === 0) &&
this.values.parsedScale
)
this.update();
) {
return this.update();
}
return this;
};
@ -335,8 +342,10 @@ export abstract class AudibleEvent extends AbstractEvent {
if (
(this.values.pitch || this.values.pitch === 0) &&
this.values.parsedScale
)
this.update();
) {
return this.update();
}
return this;
};
@ -364,16 +373,14 @@ export abstract class AudibleEvent extends AbstractEvent {
this.values.parsedScale = value.map((v) => safeScale(v));
}
this.defaultPitchKeyScale();
this.update();
return this;
return this.update();
};
semitones(values: number|number[], ...rest: number[]) {
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
this.values.parsedScale = safeScale(scaleValues);
this.defaultPitchKeyScale();
this.update();
return this;
return this.update();
}
steps = this.semitones;
@ -381,23 +388,20 @@ export abstract class AudibleEvent extends AbstractEvent {
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
this.values.parsedScale = safeScale(centsToSemitones(scaleValues));
this.defaultPitchKeyScale();
this.update();
return this;
return this.update();
}
ratios(values: number|number[], ...rest: number[]) {
const scaleValues = typeof values === "number" ? [values, ...rest] : values;
this.values.parsedScale = safeScale(ratiosToSemitones(scaleValues));
this.defaultPitchKeyScale();
this.update();
return this;
return this.update();
}
edo(value: number, intervals: string|number[] = new Array(value).fill(1)) {
this.values.parsedScale = edoToSemitones(value, intervals);
this.defaultPitchKeyScale();
this.update();
return this;
return this.update();
}
protected updateValue<T>(key: string, value: T | T[] | null): this {
@ -508,8 +512,9 @@ export abstract class AudibleEvent extends AbstractEvent {
return this;
};
update = (): void => {
update = (): this => {
// Overwrite in subclasses
return this;
};
cue = (functionName: string|Function): this => {

View File

@ -67,8 +67,7 @@ export class MidiEvent extends AudibleEvent {
return funcResult;
} else {
func(this.values);
this.update();
return this;
return this.update();
}
};
@ -84,7 +83,7 @@ export class MidiEvent extends AudibleEvent {
return this;
};
update = (): void => {
update = (): this => {
const filteredValues = filterObject(this.values, [
"key",
"pitch",
@ -113,6 +112,7 @@ export class MidiEvent extends AudibleEvent {
this.values.note = maybeAtomic(newArrays.note);
if (newArrays.bend) this.values.bend = maybeAtomic(newArrays.bend);
return this;
};
out = (): void => {

View File

@ -382,12 +382,11 @@ export class SoundEvent extends AudibleEvent {
if (funcResult instanceof Object) return funcResult;
else {
func(this.values);
this.update();
return this;
return this.update();
}
};
update = (): void => {
update = (): this => {
const filteredValues = filterObject(this.values, [
"key",
"pitch",
@ -420,7 +419,7 @@ export class SoundEvent extends AudibleEvent {
this.values.pitch = maybeAtomic(newArrays.pitch);
this.values.octave = maybeAtomic(newArrays.octave);
this.values.pitchOctave = maybeAtomic(newArrays.pitchOctave);
return this;
};
out = (orbit?: number | number[]): void => {

7181
src/colors.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -49,6 +49,9 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st
|Force Eval|${key_shortcut(
"Ctrl + Shift + Enter",
)}|Force evaluation of the current script|
|Clear cache & Eval|${key_shortcut(
"Ctrl + Shift + Backspace (Delete)",
)}|Clears cache and forces evaluation to update cached scripts|
### Special

View File

@ -32,7 +32,7 @@ export const samples_to_markdown = (
markdownList += `
<button
class="hover:bg-neutral-500 inline px-4 py-2 bg-neutral-700 text-orange-300 text-xl"
class="hover:bg-foreground inline px-4 py-2 bg-black text-brightwhite hover:text-background text-xl"
onclick="app.api._playDocExampleOnce(app.api.codeExamples['${codeId}'])"
>
${keys[i]}
@ -75,7 +75,7 @@ beat(0.5)::sound('wt_stereo').n([0, 1].pick()).ad(0, .25).out()
Pick one folder and spend some time exploring it. There is a lot of different waveforms.
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
${samples_to_markdown(application, "Waveforms")}
</div>
@ -94,7 +94,7 @@ beat(0.5)::sound(['bd', 'cp'].pick()).bank("AkaiLinn").out()
Here is the complete list of available machines:
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
${samples_to_markdown(application, "Machines")}
</div>
@ -102,7 +102,7 @@ ${samples_to_markdown(application, "Machines")}
The default sample pack used by Ryan Kirkbride's [FoxDot](https://github.com/Qirky/FoxDot). It is a nice curated sample pack that covers all the basic sounds you could want.
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
${samples_to_markdown(application, "FoxDot")}
</div>
@ -110,7 +110,7 @@ ${samples_to_markdown(application, "FoxDot")}
This set of audio samples is taken from [this wonderful collection](https://archive.org/details/AmigaSoundtrackerSamplePacksst-xx) of **Ultimate Tracker Amiga samples**. They were initially made by Karsten Obarski. These files were processed: pitched down one octave, gain down 6db. The audio has been processed with [SoX](https://github.com/chirlu/sox). The script used to do so is also included in this repository.
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
${samples_to_markdown(application, "Amiga")}
</div>
@ -128,7 +128,7 @@ beat(4)::sound('amen1').stretch(4).out()
The stretch should be adapted based on the length of each amen break.
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
${samples_to_markdown(application, "Amen")}
</div>
@ -137,7 +137,7 @@ ${samples_to_markdown(application, "Amen")}
Many live coders are expecting to find the Tidal sample library wherever they go, so here it is :)
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
${samples_to_markdown(application, "Tidal")}
</div>
@ -145,7 +145,7 @@ ${samples_to_markdown(application, "Tidal")}
This sample pack is only one folder full of french phonems! It sounds super nice.
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-neutral-600 mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
<div class="lg:pl-6 lg:pr-6 w-fit rounded-lg bg-background mx-6 mt-2 my-6 px-2 py-2 max-h-96 flex flex-row flex-wrap gap-x-2 gap-y-2 overflow-y-scroll">
${samples_to_markdown(application, "Juliette")}
</div>
`;

View File

@ -9,6 +9,24 @@ export const bonus = (application: Editor): string => {
Some features have been included as a bonus. These features are often about patterning over things that are not directly related to sound: pictures, video, etc.
## Editor theme configuration
The editor theme can be changed using the <ic>theme</ic> and <ic>randomTheme</ic> functions. The following example will use a random color scheme for every beat:
${makeExample(
"Random theme on each beat",
`
beat(1)::randomTheme()
`, true)}
You can also pick a theme using the <ic>theme</ic> function with a string as only argument:
${makeExample(
"Picking a theme",
`
beat(1)::theme("Batman")
`, true)}
## Hydra Visual Live Coding
<div class="mx-12 bg-neutral-600 rounded-lg flex flex-col items-center justify-center">

View File

@ -6,9 +6,48 @@ export const generators = (application: Editor): string => {
return `
# Generator functions
JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator" target="_blank">generators</a> are powerful functions for generating value sequences. They can be used to generate melodies, rhythms or control parameters.
In Topos generator functions should be called using the <ic>cache(key, function)</ic> function to store the current state of the generator. This function takes two arguments: the name for the cache and the generator instance.
Once the generator is cached the values will be returned from the named cache even if the generator function is modified. To clear the current cache and to re-evaluate the modified generator use the **Shift+Ctrl+Backspace** shortcut. Alternatively you can cache the modified generator using a different name.
The resulted values can be played using either <ic>pitch()</ic> or <ic>freq()</ic> or as Ziffers patterns. When playing the values using <ic>pitch()</ic> different scales and chained methods can be used to alter the result, for example <ic>mod(value: number)</ic> to limit the integer range or <ic>scale(name: string)</ic> etc. to change the resulting note.
${makeExample(
"More complex function generating chaotic frequencies",
"Simple looping generator function",
`
function* simple() {
let x = 0;
while (x < 12) {
yield x+x;
x+=1;
}
}
beat(.25) && sound("triangle").pitch(cache("simple",simple())).scale("minor").out()
`,
true,
)};
${makeExample(
"Infinite frequency generator",
`
function* poly(x=0) {
while (true) {
const s = Math.tan(x/10)+Math.sin(x/20);
yield 2 * Math.pow(s, 3) - 6 * Math.pow(s, 2) + 5 * s + 200;
x++;
}
}
beat(.125) && sound("triangle").freq(cache("mathyshit",poly())).out()
`,
true,
)};
${makeExample(
"Truly scale free chaos inspired by Lorentz attractor",
`
function* strange(x = 0.1, y = 0, z = 0, rho = 28, beta = 8 / 3, zeta = 10) {
while (true) {
@ -33,5 +72,48 @@ ${makeExample(
true,
)};
`;
## OEIS integer sequences
To find some inspiration - or to enter into the void - one can visit <a href="https://oeis.org/" target="_blank">The On-Line Encyclopedia of Integer Sequences (OEIS)</a> to find some interesting integer sequences.
Many of the sequences are implemented by <a href="https://github.com/acerix/jisg/tree/main/src/oeis" target="_blank">JISG</a> (Javascript Integer Sequence Generators) project. Those sequences can be referenced directly with the identifiers using the cache function.
One of these implemented generators is the Inventory sequence <a href="https://github.com/acerix/jisg/blob/main/src/oeis/A342585.ts" target="_blank">A342585</a> made famous by <a href="https://www.youtube.com/watch?v=rBU9E-ZOZAI" target="_blank">Neil Sloane</a>.
${makeExample(
"Inventory sequence",
`
rhythm(0.5,[8,7,5,6].bar(4),9) :: sound("triangle")
.pitch(cache("Inventory",A342585))
.mod(11).scale("minor")
.adsr(.25,.05,.5,.5)
.room(2.0).size(0.5)
.gain(1).out()
`,
true,
)};
## Using generators with Ziffers
Alternatively generators can be used with Ziffers to generate longer patterns. In this case the generator function should be passed as an argument to the ziffers function. Ziffers patterns are cached separately so there is no need for using **cache()** function. Ziffers expects values from the generators to be integers or strings in ziffers syntax.
${makeExample(
"Ziffers patterns using a generator functions",
`
function* poly(x) {
while (true) {
yield 64 * Math.pow(x, 6) - 480 * Math.pow(x, 4) + 720 * Math.pow(x, 2);
x++;
}
}
z0(poly(1)).noteLength(0.5).semitones(2,2,3,2,2,2).sound("sine").out()
z1(poly(8)).noteLength(0.25).semitones(2,1,2,1,2,2).sound("sine").out()
z2(poly(-3)).noteLength(1.0).semitones(2,2,2,1,3,2).sound("sine").out()
`,
true,
)};
`
};

View File

@ -12,14 +12,14 @@ Ziffers patterns can be synced to any event by using **cue**, **sync**, **wait**
The <ic>cue(name: string)</ic> methods can be used to send cue messages for ziffers patterns. The <ic>wait(name: string)</ic> method is used to wait for the cue message to be received before starting the next cycle.
${makeExample(
"Sending cue from event and wait",
`
beat(4.0) :: sound("bd").cue("foo").out();
z1("e 0 3 2 1 2 1").wait("foo").sound("sine").out();
`,
true,
)}
${makeExample(
"Sending cue from event and wait",
`
beat(4.0) :: sound("bd").cue("foo").out()
z1("e 0 3 2 1 2 1").wait("foo").sound("sine").out()
`,
true,
)}
The <ic>sync(name: string)</ic> method is used to sync the ziffers pattern to the cue message.

View File

@ -5,6 +5,7 @@ import { javascript } from "@codemirror/lang-javascript";
import { markdown } from "@codemirror/lang-markdown";
import { Extension } from "@codemirror/state";
import { outputSocket } from "./IO/OSC";
import { getCodeMirrorTheme } from "./EditorSetup";
import {
initializeSelectedUniverse,
AppSettings,
@ -32,6 +33,7 @@ import { installWindowBehaviors } from "./WindowBehavior";
import { makeNumberExtensions } from "./extensions/NumberExtensions";
// @ts-ignore
import { registerSW } from "virtual:pwa-register";
import colors from "./colors.json";
if ("serviceWorker" in navigator) {
registerSW();
@ -51,6 +53,7 @@ export class Editor {
hidden_interface: boolean = false;
fontSize!: Compartment;
withLineNumbers!: Compartment;
themeCompartment!: Compartment;
vimModeCompartment!: Compartment;
hoveringCompartment!: Compartment;
completionsCompartment!: Compartment;
@ -68,6 +71,7 @@ export class Editor {
public _mouseX: number = 0;
public _mouseY: number = 0;
show_error: boolean = false;
currentThemeName: string = "Everblush";
buttonElements: Record<string, HTMLButtonElement[]> = {};
interface: ElementMap = {};
blinkTimeouts: Record<number, number> = {};
@ -206,6 +210,15 @@ export class Editor {
// Loading universe from URL (if needed)
loadUniverserFromUrl(this);
// Set the color scheme for the application
let available_themes = Object.keys(colors);
if (this.settings.theme in available_themes) {
this.readTheme(this.settings.theme);
} else {
this.settings.theme = "Everblush";
this.readTheme(this.settings.theme);
}
}
private getBuffer(type: string): any {
@ -261,7 +274,7 @@ export class Editor {
let list = document.createElement("ul");
list.className =
"lg:h-80 lg:text-normal text-sm h-auto lg:w-80 w-auto lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-neutral-800";
"lg:h-80 lg:text-normal text-normal h-auto lg:w-80 w-auto lg:pb-2 lg:pt-2 overflow-y-scroll text-brightwhite bg-background lg:mb-4 border rounded-lg";
list.append(
...Object.keys(this.universes).map((it) => {
let item = itemTemplate.content.cloneNode(true) as DocumentFragment;
@ -293,9 +306,9 @@ export class Editor {
*/
const tabs = document.querySelectorAll('[id^="tab-"]');
const tab = tabs[i] as HTMLElement;
tab.classList.add("bg-orange-300");
tab.classList.add("bg-foreground");
for (let j = 0; j < tabs.length; j++) {
if (j != i) tabs[j].classList.remove("bg-orange-300");
if (j != i) tabs[j].classList.remove("bg-foreground_selection");
}
let tab_id = tab.id.split("-")[1];
this.local_index = parseInt(tab_id);
@ -318,15 +331,15 @@ export class Editor {
let changeColor = (button: HTMLElement) => {
interface_buttons.forEach((button) => {
let svg = button.children[0] as HTMLElement;
if (svg.classList.contains("text-orange-300")) {
svg.classList.remove("text-orange-300");
button.classList.remove("text-orange-300");
if (svg.classList.contains("text-foreground_selection")) {
svg.classList.remove("text-foreground_selection");
button.classList.remove("text-foreground_selection");
}
});
button.children[0].classList.remove("text-white");
button.children[0].classList.add("text-orange-300");
button.classList.add("text-orange-300");
button.classList.add("fill-orange-300");
button.children[0].classList.add("text-foreground_selection");
button.classList.add("text-foreground_selection");
button.classList.add("fill-foreground_selection");
};
switch (mode) {
@ -441,7 +454,7 @@ export class Editor {
unfocusPlayButtons() {
document.querySelectorAll('[id^="play-button-"]').forEach((button) => {
button.children[0].classList.remove("fill-orange-300");
button.children[0].classList.remove("fill-foreground_selection");
button.children[0].classList.remove("animate-pulse");
});
}
@ -570,6 +583,45 @@ export class Editor {
ctx.scale(dpr, dpr);
}
}
private updateInterfaceTheme(selected_theme: {[key: string]: string}): void {
function hexToRgb(hex: string): {r: number, g: number, b: number} | null {
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
};
for (const [key, value] of Object.entries(selected_theme)) {
let color = hexToRgb(value);
if (color) {
let colorString = `${color.r} ${color.g} ${color.b}`
document.documentElement.style.setProperty("--" + key, colorString);
}
}
}
getColorScheme(theme_name: string): {[key: string]: string} {
// Check if the theme exists in colors.json
let themes: Record<string, { [key: string]: any }> = colors;
return themes[theme_name];
}
readTheme(theme_name: string): void {
// Check if the theme exists in colors.json
let themes: Record<string, { [key: string]: any }> = colors;
let selected_theme = themes[theme_name];
if (selected_theme) {
this.currentThemeName = theme_name;
this.updateInterfaceTheme(selected_theme);
let codeMirrorTheme = getCodeMirrorTheme(selected_theme);
// Reconfigure the view with the new theme
this.view.dispatch({
effects: this.themeCompartment.reconfigure(codeMirrorTheme),
});
}
}
}
let app = new Editor();

File diff suppressed because it is too large Load Diff

29
src/tailwind.css Normal file
View File

@ -0,0 +1,29 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--black: 40 42 54;
--red: 68 71 90;
--green: 248 248 242;
--yellow: 98 114 164;
--blue: 139 233 253;
--magenta: 80 250 123;
--cyan: 255 184 108;
--white: 255 121 198;
--brightblack: 189 147 249;
--brightred: 255 85 85;
--brightgreen: 241 250 140;
--brightyellow: 139 233 253;
--brightblue: 80 250 123;
--brightmagenta: 255 184 108;
--brightcyan: 255 121 198;
--brightwhite: 189 147 249;
--background: 40 42 54;
--selection_foreground: 68 71 90;
--cursor: 139 233 253;
--foreground: 248 248 242;
--selection_background: 189 147 249;
}
}

View File

@ -1,221 +0,0 @@
import { EditorView } from "@codemirror/view";
import { Extension } from "@codemirror/state";
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { tags as t } from "@lezer/highlight";
const base00 = "#262626",
base01 = "#3B4252",
base02 = "#BBBBBB",
base03 = "#4C566A",
base04 = "#D8DEE9",
base05 = "#E5E9F0",
base07 = "#8FBCBB",
base_red = "#BF616A",
base_deeporange = "#D08770",
base_pink = "#B48EAD",
base_cyan = "#FBCF8B",
base_yellow = "#88C0D0",
base_orange = "#D08770",
base_indigo = "#5E81AC",
base_purple = "#B48EAD",
base_green = "#A3BE8C",
base_lightgreen = "#A3BE8C";
const invalid = base_red,
darkBackground = "#262626",
highlightBackground = "#252525",
// background = base00,
tooltipBackground = base01,
cursor = base04;
/// The editor theme styles for Material Dark.
export const toposDarkTheme = EditorView.theme(
{
"&": {
color: base05,
// backgroundColor: background,
backgroundColor: "transparent",
fontSize: "24px",
fontFamily: "IBM Plex Mono",
},
".cm-content": {
caretColor: cursor,
fontFamily: "IBM Plex Mono",
},
".cm-cursor, .cm-dropCursor": {
borderLeftColor: cursor,
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
{
backgroundColor: base00,
border: `0.5px solid ${base00}`,
},
".cm-panels": {
backgroundColor: darkBackground,
color: base05,
},
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
".cm-search.cm-panel": { backgroundColor: "transparent" },
".cm-searchMatch": {
outline: `1px solid ${base_cyan}`,
},
".cm-searchMatch.cm-searchMatch-selected": {
backgroundColor: highlightBackground,
},
".cm-activeLine": {
// backgroundColor: highlightBackground
backgroundColor: "rgb(76,76,106, 0.1)",
},
".cm-selectionMatch": {
backgroundColor: base04,
outline: `1px solid ${base_red}`,
},
"&.cm-focused .cm-matchingBracket": {
color: base02,
// outline: `1px solid ${base02}`,
},
"&.cm-focused .cm-nonmatchingBracket": {
color: base_red,
},
".cm-gutters": {
//backgroundColor: base00,
backgroundColor: "transparent",
color: base02,
},
".cm-activeLineGutter": {
backgroundColor: highlightBackground,
color: base02,
},
".cm-foldPlaceholder": {
border: "none",
color: `${base07}`,
},
".cm-tooltip": {
border: "none",
backgroundColor: tooltipBackground,
},
".cm-tooltip .cm-tooltip-arrow:before": {},
".cm-tooltip .cm-tooltip-arrow:after": {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground,
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: highlightBackground,
color: base03,
},
},
},
{ dark: true },
);
/// The highlighting style for code in the Material Dark theme.
export const toposDarkHighlightStyle = HighlightStyle.define([
{ tag: t.keyword, color: base_purple },
{
tag: [t.name, t.deleted, t.character, t.macroName],
color: base_cyan,
},
{ tag: [t.propertyName], color: base_yellow },
{ tag: [t.variableName], color: base05 },
{ tag: [t.function(t.variableName)], color: base_cyan },
{ tag: [t.labelName], color: base_purple },
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: base_yellow,
},
{ tag: [t.definition(t.name), t.separator], color: base_pink },
{ tag: [t.brace], color: base_purple },
{
tag: [t.annotation],
color: invalid,
},
{
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: base_orange,
},
{
tag: [t.typeName, t.className],
color: base_orange,
},
{
tag: [t.operator, t.operatorKeyword],
color: base_indigo,
},
{
tag: [t.tagName],
color: base_deeporange,
},
{
tag: [t.squareBracket],
color: base_red,
},
{
tag: [t.angleBracket],
color: base02,
},
{
tag: [t.attributeName],
color: base05,
},
{
tag: [t.regexp],
color: invalid,
},
{
tag: [t.quote],
color: base_green,
},
{ tag: [t.string], color: base_lightgreen },
{
tag: t.link,
color: base_cyan,
textDecoration: "underline",
textUnderlinePosition: "under",
},
{
tag: [t.url, t.escape, t.special(t.string)],
color: base_yellow,
},
{ tag: [t.meta], color: base03 },
{ tag: [t.comment], color: base02, fontStyle: "italic" },
{ tag: t.monospace, color: base05 },
{ tag: t.strong, fontWeight: "bold", color: base_red },
{ tag: t.emphasis, fontStyle: "italic", color: base_lightgreen },
{ tag: t.strikethrough, textDecoration: "line-through" },
{ tag: t.heading, fontWeight: "bold", color: base_yellow },
{ tag: t.heading1, fontWeight: "bold", color: base_yellow },
{
tag: [t.heading2, t.heading3, t.heading4],
fontWeight: "bold",
color: base_yellow,
},
{
tag: [t.heading5, t.heading6],
color: base_yellow,
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: base_cyan },
{
tag: [t.processingInstruction, t.inserted],
color: base_red,
},
{
tag: [t.contentSeparator],
color: base_cyan,
},
{ tag: t.invalid, color: base02, borderBottom: `1px dotted ${base_red}` },
]);
/// Extension to enable the Material Dark theme (both the editor theme and
/// the highlight style).
export const toposTheme: Extension = [
toposDarkTheme,
syntaxHighlighting(toposDarkHighlightStyle),
];