import { EditorView } from "codemirror"; import { vim } from "@replit/codemirror-vim"; import { type Editor } from "./main"; import colors from "./colors.json"; import { documentation_factory, documentation_pages, 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 { saveState } from "./WindowBehavior"; import { registerSamplesFromDB, samplesDBConfig, uploadSamplesToDB } from "./IO/SampleLoading"; export const installInterfaceLogic = (app: Editor) => { // Initialize style (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-foreground"); tabs[i].classList.add("text-selection_foreground"); for (let j = 0; j < tabs.length; j++) { if (j != i) tabs[j].classList.remove("bg-foreground"); if (j != i) tabs[j].classList.remove("text-selection_foreground"); } 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("keydown", (event: any) => { if (event.key === "Enter") { let content = ( app.interface.universe_viewer as HTMLInputElement ).value.trim(); if (content.length > 2 && content.length < 40) { if (content !== app.selected_universe) { Object.defineProperty( app.universes, content, // @ts-ignore Object.getOwnPropertyDescriptor( app.universes, app.selected_universe, ), ); delete app.universes[app.selected_universe]; } app.selected_universe = content; loadUniverse(app, app.selected_universe); (app.interface.universe_viewer as HTMLInputElement).placeholder = content; (app.interface.universe_viewer as HTMLInputElement).value = ""; } } }); app.interface.audio_nudge_range.addEventListener("input", () => { // TODO: rebuild this // 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_samples_button.addEventListener("input", async (event) => { let fileInput = event.target as HTMLInputElement; if (!fileInput.files?.length) { return; } app.interface.sample_indicator.innerText = "Loading..."; app.interface.sample_indicator.classList.add("animate-pulse"); await uploadSamplesToDB(samplesDBConfig, fileInput.files).then(() => { registerSamplesFromDB(samplesDBConfig, () => { app.interface.sample_indicator.innerText = "Import samples"; app.interface.sample_indicator.classList.remove("animate-pulse"); }); }); }); 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.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 += `
█`; } app.interface.theme_previewer.innerHTML = theme_preview; }); 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; } 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 `` }).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 = "
█
`; } theme_preview += "