519 lines
17 KiB
TypeScript
519 lines
17 KiB
TypeScript
import { EditorView } from "codemirror";
|
|
import { vim } from "@replit/codemirror-vim";
|
|
import { type Editor } from "./main";
|
|
import {
|
|
documentation_factory,
|
|
hideDocumentation,
|
|
showDocumentation,
|
|
updateDocumentationContent,
|
|
} from "./Documentation";
|
|
import {
|
|
type Universe,
|
|
template_universe,
|
|
template_universes,
|
|
loadUniverse,
|
|
emptyUrl,
|
|
share,
|
|
closeUniverseModal,
|
|
openUniverseModal,
|
|
} from "./FileManagement";
|
|
import { loadSamples } from "./API";
|
|
import { tryEvaluate } from "./Evaluator";
|
|
import { inlineHoveringTips } from "./documentation/inlineHelp";
|
|
import { lineNumbers } from "@codemirror/view";
|
|
import { jsCompletions } from "./EditorSetup";
|
|
import { createDocumentationStyle } from "./DomElements";
|
|
import { saveState } from "./WindowBehavior";
|
|
|
|
export const installInterfaceLogic = (app: Editor) => {
|
|
// Initialize style
|
|
const documentationStyle = createDocumentationStyle(app);
|
|
const bindings = Object.keys(documentationStyle).map((key) => ({
|
|
type: "output",
|
|
regex: new RegExp(`<${key}([^>]*)>`, "g"),
|
|
//@ts-ignore
|
|
replace: (match, p1) => `<${key} class="${documentationStyle[key]}" ${p1}>`,
|
|
}));
|
|
|
|
(app.interface.line_numbers_checkbox as HTMLInputElement).checked =
|
|
app.settings.line_numbers;
|
|
(app.interface.time_position_checkbox as HTMLInputElement).checked =
|
|
app.settings.time_position;
|
|
(app.interface.tips_checkbox as HTMLInputElement).checked = app.settings.tips;
|
|
(app.interface.completion_checkbox as HTMLInputElement).checked =
|
|
app.settings.completions;
|
|
|
|
(app.interface.midi_clock_checkbox as HTMLInputElement).checked =
|
|
app.settings.send_clock;
|
|
(app.interface.midi_channels_scripts as HTMLInputElement).checked =
|
|
app.settings.midi_channels_scripts;
|
|
(app.interface.midi_clock_ppqn as HTMLInputElement).value =
|
|
app.settings.midi_clock_ppqn.toString();
|
|
(app.interface.load_demo_songs as HTMLInputElement).checked =
|
|
app.settings.load_demo_songs;
|
|
|
|
const tabs = document.querySelectorAll('[id^="tab-"]');
|
|
// Iterate over the tabs with an index
|
|
for (let i = 0; i < tabs.length; i++) {
|
|
tabs[i].addEventListener("click", (event) => {
|
|
// Updating the CSS accordingly
|
|
tabs[i].classList.add("bg-orange-300");
|
|
for (let j = 0; j < tabs.length; j++) {
|
|
if (j != i) tabs[j].classList.remove("bg-orange-300");
|
|
}
|
|
app.currentFile().candidate = app.view.state.doc.toString();
|
|
|
|
let tab = event.target as HTMLElement;
|
|
let tab_id = tab.id.split("-")[1];
|
|
app.local_index = parseInt(tab_id);
|
|
app.updateEditorView();
|
|
});
|
|
}
|
|
|
|
app.interface.topos_logo.addEventListener("click", () => {
|
|
hideDocumentation();
|
|
app.updateKnownUniversesView();
|
|
openUniverseModal();
|
|
});
|
|
|
|
app.buttonElements.play_buttons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
if (app.isPlaying) {
|
|
app.setButtonHighlighting("pause", true);
|
|
app.isPlaying = !app.isPlaying;
|
|
app.clock.pause();
|
|
app.api.MidiConnection.sendStopMessage();
|
|
} else {
|
|
app.setButtonHighlighting("play", true);
|
|
app.isPlaying = !app.isPlaying;
|
|
app.clock.start();
|
|
app.api.MidiConnection.sendStartMessage();
|
|
}
|
|
});
|
|
});
|
|
|
|
app.buttonElements.clear_buttons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
app.setButtonHighlighting("clear", true);
|
|
if (confirm("Do you want to reset the current universe?")) {
|
|
app.universes[app.selected_universe] =
|
|
structuredClone(template_universe);
|
|
app.updateEditorView();
|
|
}
|
|
});
|
|
});
|
|
|
|
app.interface.documentation_button.addEventListener("click", () => {
|
|
showDocumentation(app);
|
|
});
|
|
|
|
app.interface.destroy_universes_button.addEventListener("click", () => {
|
|
if (confirm("Do you want to destroy all universes?")) {
|
|
app.universes = {
|
|
...template_universes,
|
|
};
|
|
app.updateKnownUniversesView();
|
|
}
|
|
});
|
|
|
|
app.interface.universe_viewer.addEventListener("input", () => {
|
|
let content = app.interface.universe_viewer.value as string;
|
|
content = content.trim();
|
|
if (content.length > 2 && content.length < 40) {
|
|
if (content !== app.selected_universe) {
|
|
Object.defineProperty(app.universes, content,
|
|
Object.getOwnPropertyDescriptor(app.universes, app.selected_universe));
|
|
delete app.universes[app.selected_universe];
|
|
}
|
|
app.selected_universe = content.trim();
|
|
loadUniverse(app, app.selected_universe)
|
|
}
|
|
})
|
|
|
|
app.interface.audio_nudge_range.addEventListener("input", () => {
|
|
app.clock.nudge = parseInt(
|
|
(app.interface.audio_nudge_range as HTMLInputElement).value
|
|
);
|
|
});
|
|
|
|
app.interface.dough_nudge_range.addEventListener("input", () => {
|
|
app.dough_nudge = parseInt(
|
|
(app.interface.dough_nudge_range as HTMLInputElement).value
|
|
);
|
|
});
|
|
|
|
app.interface.upload_universe_button.addEventListener("click", () => {
|
|
const fileInput = document.createElement("input");
|
|
fileInput.type = "file";
|
|
fileInput.accept = ".json";
|
|
|
|
fileInput.addEventListener("change", (event) => {
|
|
const input = event.target as HTMLInputElement;
|
|
const file = input.files?.[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.readAsText(file, "UTF-8");
|
|
|
|
reader.onload = (evt) => {
|
|
const data = JSON.parse(evt.target!.result as string);
|
|
for (const [key, value] of Object.entries(data)) {
|
|
app.universes[key] = value as Universe;
|
|
}
|
|
};
|
|
reader.onerror = (evt) => {
|
|
console.error("An error occurred reading the file:", evt);
|
|
};
|
|
}
|
|
});
|
|
|
|
document.body.appendChild(fileInput);
|
|
fileInput.click();
|
|
document.body.removeChild(fileInput);
|
|
});
|
|
|
|
app.interface.download_universe_button.addEventListener("click", () => {
|
|
// Trigger save of the universe before downloading
|
|
app.settings.saveApplicationToLocalStorage(app.universes, app.settings);
|
|
|
|
// Generate a file name based on timestamp
|
|
let fileName = `topos-universes-${Date.now()}.json`;
|
|
|
|
// Create Blob and Object URL
|
|
const blob = new Blob([JSON.stringify(app.settings.universes)], {
|
|
type: "application/json",
|
|
});
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
// Create a temporary anchor and trigger download
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = fileName;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
|
|
// Revoke the Object URL to free resources
|
|
URL.revokeObjectURL(url);
|
|
});
|
|
|
|
app.interface.load_universe_button.addEventListener("click", () => {
|
|
let query = (app.interface.buffer_search as HTMLInputElement).value;
|
|
if (query.length > 2 && query.length < 20 && !query.includes(" ")) {
|
|
app.settings.selected_universe = query;
|
|
loadUniverse(app, query);
|
|
(app.interface.buffer_search as HTMLInputElement).value = "";
|
|
closeUniverseModal();
|
|
app.view.focus();
|
|
emptyUrl();
|
|
}
|
|
});
|
|
|
|
app.interface.eval_button.addEventListener("click", () => {
|
|
app.currentFile().candidate = app.view.state.doc.toString();
|
|
app.flashBackground("#404040", 200);
|
|
});
|
|
|
|
app.buttonElements.stop_buttons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
app.setButtonHighlighting("stop", true);
|
|
app.isPlaying = false;
|
|
app.clock.stop();
|
|
});
|
|
});
|
|
|
|
app.interface.local_button.addEventListener("click", () =>
|
|
app.changeModeFromInterface("local")
|
|
);
|
|
app.interface.global_button.addEventListener("click", () =>
|
|
app.changeModeFromInterface("global")
|
|
);
|
|
app.interface.init_button.addEventListener("click", () =>
|
|
app.changeModeFromInterface("init")
|
|
);
|
|
app.interface.note_button.addEventListener("click", () =>
|
|
app.changeModeFromInterface("notes")
|
|
);
|
|
|
|
app.interface.font_family_selector.addEventListener("change", () => {
|
|
//@ts-ignore
|
|
let new_font = (app.interface.font_family_selector as HTMLSelectElement)
|
|
.value;
|
|
console.log("Picking new font : " + new_font);
|
|
app.settings.font = new_font;
|
|
|
|
app.view.dispatch({
|
|
effects: app.fontSize.reconfigure(
|
|
EditorView.theme({
|
|
"&": { fontSize: app.settings.font_size + "px" },
|
|
".cm-content": {
|
|
fontFamily: new_font,
|
|
fontSize: app.settings.font_size + "px",
|
|
},
|
|
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
|
})
|
|
),
|
|
});
|
|
});
|
|
|
|
app.interface.font_size_input.addEventListener("input", () => {
|
|
let new_value: string | number = (
|
|
app.interface.font_size_input as HTMLInputElement
|
|
).value;
|
|
app.settings.font_size = parseInt(new_value);
|
|
// TODO: update the font size
|
|
app.view.dispatch({
|
|
effects: app.fontSize.reconfigure(
|
|
EditorView.theme({
|
|
"&": { fontSize: app.settings.font_size + "px" },
|
|
".cm-content": {
|
|
fontFamily: app.settings.font,
|
|
fontSize: app.settings.font_size + "px",
|
|
},
|
|
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
|
})
|
|
),
|
|
});
|
|
});
|
|
|
|
app.interface.settings_button.addEventListener("click", () => {
|
|
// Populate the font selector
|
|
const fontFamilySelect = document.getElementById(
|
|
"font-family"
|
|
) as HTMLSelectElement | null;
|
|
if (fontFamilySelect) {
|
|
fontFamilySelect.value = app.settings.font;
|
|
}
|
|
|
|
// Populate the font family selector
|
|
const doughNudgeRange = app.interface.dough_nudge_range as HTMLInputElement;
|
|
doughNudgeRange.value = app.dough_nudge.toString();
|
|
// @ts-ignore
|
|
const doughNumber = document.getElementById(
|
|
"doughnumber"
|
|
) as HTMLInputElement;
|
|
doughNumber.value = app.dough_nudge.toString();
|
|
if (app.settings.font_size === null) {
|
|
app.settings.font_size = 12;
|
|
}
|
|
const fontSizeInput = app.interface.font_size_input as HTMLInputElement;
|
|
fontSizeInput.value = app.settings.font_size.toString();
|
|
|
|
// Get the right value to update graphical widgets
|
|
const lineNumbersCheckbox = app.interface
|
|
.line_numbers_checkbox as HTMLInputElement;
|
|
lineNumbersCheckbox.checked = app.settings.line_numbers;
|
|
const timePositionCheckbox = app.interface
|
|
.time_position_checkbox as HTMLInputElement;
|
|
timePositionCheckbox.checked = app.settings.time_position;
|
|
const tipsCheckbox = app.interface.tips_checkbox as HTMLInputElement;
|
|
tipsCheckbox.checked = app.settings.tips;
|
|
const midiClockCheckbox = app.interface
|
|
.midi_clock_checkbox as HTMLInputElement;
|
|
midiClockCheckbox.checked = app.settings.send_clock;
|
|
const midiChannelsScripts = app.interface
|
|
.midi_channels_scripts as HTMLInputElement;
|
|
midiChannelsScripts.checked = app.settings.midi_channels_scripts;
|
|
const midiClockPpqn = app.interface.midi_clock_ppqn as HTMLInputElement;
|
|
midiClockPpqn.value = app.settings.midi_clock_ppqn.toString();
|
|
const loadDemoSongs = app.interface.load_demo_songs as HTMLInputElement;
|
|
loadDemoSongs.checked = app.settings.load_demo_songs;
|
|
const vimModeCheckbox = app.interface.vim_mode_checkbox as HTMLInputElement;
|
|
vimModeCheckbox.checked = app.settings.vimMode;
|
|
|
|
let modal_settings = document.getElementById("modal-settings");
|
|
let editor = document.getElementById("editor");
|
|
modal_settings?.classList.remove("invisible");
|
|
|
|
editor?.classList.add("invisible");
|
|
});
|
|
|
|
app.interface.close_settings_button.addEventListener("click", () => {
|
|
let modal_settings = document.getElementById("modal-settings");
|
|
let editor = document.getElementById("editor");
|
|
modal_settings?.classList.add("invisible");
|
|
editor?.classList.remove("invisible");
|
|
let new_value: string = (app.interface.font_size_input as HTMLInputElement)
|
|
.value;
|
|
app.settings.font_size = parseInt(new_value);
|
|
// Update fontSize compartment with new font size value
|
|
app.view.dispatch({
|
|
effects: app.fontSize.reconfigure(
|
|
EditorView.theme({
|
|
"&": { fontSize: app.settings.font_size + "px" },
|
|
".cm-content": {
|
|
fontFamily: app.settings.font,
|
|
fontSize: app.settings.font_size + "px",
|
|
},
|
|
".cm-gutters": { fontSize: app.settings.font_size + "px" },
|
|
})
|
|
),
|
|
});
|
|
});
|
|
|
|
app.interface.close_universes_button.addEventListener("click", () => {
|
|
saveState(app);
|
|
openUniverseModal();
|
|
});
|
|
|
|
app.interface.share_button.addEventListener("click", async () => {
|
|
// trigger a manual save
|
|
app.currentFile().candidate = app.view.state.doc.toString();
|
|
app.currentFile().committed = app.view.state.doc.toString();
|
|
app.settings.saveApplicationToLocalStorage(app.universes, app.settings);
|
|
// encode as a blob!
|
|
await share(app);
|
|
});
|
|
|
|
app.interface.vim_mode_checkbox.addEventListener("change", () => {
|
|
let checked = (app.interface.vim_mode_checkbox as HTMLInputElement).checked
|
|
? true
|
|
: false;
|
|
app.settings.vimMode = checked;
|
|
app.view.dispatch({
|
|
effects: app.vimModeCompartment.reconfigure(checked ? vim() : []),
|
|
});
|
|
});
|
|
|
|
app.interface.line_numbers_checkbox.addEventListener("change", () => {
|
|
let lineNumbersCheckbox = app.interface
|
|
.line_numbers_checkbox as HTMLInputElement;
|
|
let checked = lineNumbersCheckbox.checked ? true : false;
|
|
app.settings.line_numbers = checked;
|
|
app.view.dispatch({
|
|
effects: app.withLineNumbers.reconfigure(checked ? [lineNumbers()] : []),
|
|
});
|
|
});
|
|
|
|
app.interface.time_position_checkbox.addEventListener("change", () => {
|
|
let timeviewer = document.getElementById("timeviewer") as HTMLElement;
|
|
let checked = (app.interface.time_position_checkbox as HTMLInputElement)
|
|
.checked
|
|
? true
|
|
: false;
|
|
app.settings.time_position = checked;
|
|
checked
|
|
? timeviewer.classList.remove("hidden")
|
|
: timeviewer.classList.add("hidden");
|
|
});
|
|
|
|
app.interface.tips_checkbox.addEventListener("change", () => {
|
|
let checked = (app.interface.tips_checkbox as HTMLInputElement).checked
|
|
? true
|
|
: false;
|
|
app.settings.tips = checked;
|
|
app.view.dispatch({
|
|
effects: app.hoveringCompartment.reconfigure(
|
|
checked ? inlineHoveringTips : []
|
|
),
|
|
});
|
|
});
|
|
|
|
app.interface.completion_checkbox.addEventListener("change", () => {
|
|
let checked = (app.interface.completion_checkbox as HTMLInputElement)
|
|
.checked
|
|
? true
|
|
: false;
|
|
app.settings.completions = checked;
|
|
app.view.dispatch({
|
|
effects: app.completionsCompartment.reconfigure(
|
|
checked ? jsCompletions : []
|
|
),
|
|
});
|
|
});
|
|
|
|
app.interface.midi_clock_checkbox.addEventListener("change", () => {
|
|
let checked = (app.interface.midi_clock_checkbox as HTMLInputElement)
|
|
.checked
|
|
? true
|
|
: false;
|
|
app.settings.send_clock = checked;
|
|
});
|
|
|
|
app.interface.midi_channels_scripts.addEventListener("change", () => {
|
|
let checked = (app.interface.midi_channels_scripts as HTMLInputElement)
|
|
.checked
|
|
? true
|
|
: false;
|
|
app.settings.midi_channels_scripts = checked;
|
|
});
|
|
|
|
app.interface.midi_clock_ppqn.addEventListener("change", () => {
|
|
let value = parseInt(
|
|
(app.interface.midi_clock_ppqn as HTMLInputElement).value
|
|
);
|
|
app.settings.midi_clock_ppqn = value;
|
|
});
|
|
|
|
app.interface.load_demo_songs.addEventListener("change", () => {
|
|
let checked = (app.interface.load_demo_songs as HTMLInputElement).checked
|
|
? true
|
|
: false;
|
|
app.settings.load_demo_songs = checked;
|
|
});
|
|
|
|
app.interface.universe_creator.addEventListener("submit", (event) => {
|
|
event.preventDefault();
|
|
|
|
let data = new FormData(app.interface.universe_creator as HTMLFormElement);
|
|
let universeName = data.get("universe") as string | null;
|
|
|
|
if (universeName) {
|
|
if (universeName.length > 2 && universeName.length < 20) {
|
|
universeName = universeName.trim();
|
|
app.settings.selected_universe = universeName;
|
|
app.selected_universe = universeName;
|
|
loadUniverse(app, universeName);
|
|
(app.interface.buffer_search as HTMLInputElement).value = "";
|
|
closeUniverseModal();
|
|
app.view.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
tryEvaluate(app, app.universes[app.selected_universe.toString()].init);
|
|
|
|
[
|
|
"introduction",
|
|
"interface",
|
|
"interaction",
|
|
"code",
|
|
"time",
|
|
"linear",
|
|
"cyclic",
|
|
"longform",
|
|
"sound",
|
|
"samples",
|
|
"synths",
|
|
"chaining",
|
|
"patterns",
|
|
"ziffers",
|
|
"midi",
|
|
"functions",
|
|
"lfos",
|
|
"probabilities",
|
|
"variables",
|
|
// "reference",
|
|
"synchronisation",
|
|
"mouse",
|
|
"shortcuts",
|
|
"about",
|
|
"bonus",
|
|
"oscilloscope",
|
|
].forEach((e) => {
|
|
let name = `docs_` + e;
|
|
document.getElementById(name)!.addEventListener("click", async () => {
|
|
if (name !== "docs_samples") {
|
|
app.currentDocumentationPane = e;
|
|
updateDocumentationContent(app, bindings);
|
|
} else {
|
|
console.log("Loading samples!");
|
|
await loadSamples().then(() => {
|
|
app.docs = documentation_factory(app);
|
|
app.currentDocumentationPane = e;
|
|
updateDocumentationContent(app, bindings);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
};
|