Small QoL things

This commit is contained in:
2023-07-29 10:03:28 +02:00
parent df1a8b2357
commit b2dc465bcb
2 changed files with 388 additions and 295 deletions

View File

@ -30,6 +30,8 @@
} }
</style> </style>
<body> <body>
<!-- The header is hidden on smaller devices -->
<header class="py-2 hidden xl:block text-white bg-neutral-900"> <header class="py-2 hidden xl:block text-white bg-neutral-900">
<div class="mx-auto flex flex-wrap pl-2 py-1 flex-row items-center"> <div class="mx-auto flex flex-wrap pl-2 py-1 flex-row items-center">
<a class="flex title-font font-medium items-center text-black mb-0"> <a class="flex title-font font-medium items-center text-black mb-0">
@ -84,7 +86,13 @@
<div id="modal-container" class="motion-safe:animate-pulse flex min-h-screen flex flex-col"> <div id="modal-container" class="motion-safe:animate-pulse flex min-h-screen flex flex-col">
<div id="modal" class="bg-neutral-900 bg-opacity-50 flex justify-center items-center absolute top-0 right-0 bottom-0 left-0"> <div id="modal" class="bg-neutral-900 bg-opacity-50 flex justify-center items-center absolute top-0 right-0 bottom-0 left-0">
<div id="start-button" class="bg-white px-16 py-14 rounded-md text-center"> <div id="start-button" class="bg-white px-16 py-14 rounded-md text-center">
<h1 class="text-xl mb-4 font-bold text-black">Topos Prototype</h1> <h1 class="text-xl mb-4 font-bold text-white rounded bg-black">Topos Prototype</h1>
<div id="social-links" class="pb-4 font-bold flex flex-row space-x-4 justify-center">
<a href="https://google.fr">Website</a>
<a href="https://google.fr">Discord</a>
<a href="https://google.fr">Documentation</a>
<a href="https://google.fr">About</a>
</div>
<div id="disclaimer" class="pb-4"> <div id="disclaimer" class="pb-4">
<p>This is Topos, an experimental live coding platform.</p> <p>This is Topos, an experimental live coding platform.</p>
@ -95,7 +103,17 @@
</div> </div>
<div class="flex flex-row"> <div class="flex flex-row">
<aside class="flex flex-col items-center w-14 h-screen py-2 overflow-y-auto bg-white border-r rtl:border-l rtl:border-r-0 dark:bg-neutral-900 dark:border-gray-700">
<!-- This is a lateral bar that will inherit the header buttons
if the window is too small.
-->
<aside class="
flex flex-col items-center w-14
h-screen py-2 overflow-y-auto
border-r rtl:border-l
rtl:border-r-0 bg-neutral-900
dark:border-gray-700"
>
<nav class="flex flex-col space-y-6"> <nav class="flex flex-col space-y-6">
<a id="local-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100"> <a id="local-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
<svg class="w-8 h-8 text-orange-300" fill="currentColor" viewBox="0 0 20 20"> <svg class="w-8 h-8 text-orange-300" fill="currentColor" viewBox="0 0 20 20">

View File

@ -1,81 +1,98 @@
import './style.css' import "./style.css";
import { EditorView } from "codemirror"; import { EditorView } from "codemirror";
import { editorSetup } from './EditorSetup'; import { editorSetup } from "./EditorSetup";
import { EditorState, Compartment } from "@codemirror/state"; import { EditorState, Compartment } from "@codemirror/state";
import { javascript } from "@codemirror/lang-javascript"; import { javascript } from "@codemirror/lang-javascript";
import { Clock } from './Clock' import { Clock } from "./Clock";
import { vim } from "@replit/codemirror-vim"; import { vim } from "@replit/codemirror-vim";
import { AppSettings } from './AppSettings'; import { AppSettings } from "./AppSettings";
import { ViewUpdate } from '@codemirror/view'; import { ViewUpdate } from "@codemirror/view";
import { import {
highlightSelection, highlightSelection,
unhighlightSelection, unhighlightSelection,
rangeHighlighting rangeHighlighting,
} from "./highlightSelection"; } from "./highlightSelection";
import { UserAPI } from './API'; import { UserAPI } from "./API";
import { Extension } from '@codemirror/state'; import { Extension } from "@codemirror/state";
import { Universes, File, template_universe, template_universes } from './AppSettings'; import {
import { tryEvaluate } from './Evaluator'; Universes,
File,
template_universe,
template_universes,
} from "./AppSettings";
import { tryEvaluate } from "./Evaluator";
export class Editor { export class Editor {
// Data structures for editor text management // Data structures for editor text management
universes: Universes = template_universes universes: Universes = template_universes;
selected_universe: string selected_universe: string;
local_index: number = 1 local_index: number = 1;
editor_mode: 'global' | 'local' | 'init' = 'local' editor_mode: "global" | "local" | "init" = "local";
settings = new AppSettings() settings = new AppSettings();
editorExtensions: Extension[] = [] editorExtensions: Extension[] = [];
userPlugins: Extension[] = [] userPlugins: Extension[] = [];
state: EditorState state: EditorState;
api: UserAPI api: UserAPI;
// Audio stuff // Audio stuff
audioContext: AudioContext audioContext: AudioContext;
view: EditorView view: EditorView;
clock: Clock clock: Clock;
manualPlay: boolean = false manualPlay: boolean = false;
// Transport elements // Transport elements
play_buttons: HTMLButtonElement[] = [ play_buttons: HTMLButtonElement[] = [
document.getElementById('play-button-1') as HTMLButtonElement, document.getElementById("play-button-1") as HTMLButtonElement,
document.getElementById('play-button-2') as HTMLButtonElement document.getElementById("play-button-2") as HTMLButtonElement,
] ];
pause_buttons: HTMLButtonElement[] = [ pause_buttons: HTMLButtonElement[] = [
document.getElementById('pause-button-1') as HTMLButtonElement, document.getElementById("pause-button-1") as HTMLButtonElement,
document.getElementById('pause-button-2') as HTMLButtonElement document.getElementById("pause-button-2") as HTMLButtonElement,
] ];
clear_buttons: HTMLButtonElement[] = [ clear_buttons: HTMLButtonElement[] = [
document.getElementById('clear-button-1') as HTMLButtonElement, document.getElementById("clear-button-1") as HTMLButtonElement,
document.getElementById('clear-button-2') as HTMLButtonElement document.getElementById("clear-button-2") as HTMLButtonElement,
] ];
// Script selection elements // Script selection elements
local_button: HTMLButtonElement = document.getElementById('local-button') as HTMLButtonElement local_button: HTMLButtonElement = document.getElementById(
global_button: HTMLButtonElement = document.getElementById('global-button') as HTMLButtonElement "local-button"
init_button: HTMLButtonElement = document.getElementById('init-button') as HTMLButtonElement ) as HTMLButtonElement;
universe_viewer: HTMLDivElement = document.getElementById('universe-viewer') as HTMLDivElement global_button: HTMLButtonElement = document.getElementById(
"global-button"
) as HTMLButtonElement;
init_button: HTMLButtonElement = document.getElementById(
"init-button"
) as HTMLButtonElement;
universe_viewer: HTMLDivElement = document.getElementById(
"universe-viewer"
) as HTMLDivElement;
// Buffer modal // Buffer modal
buffer_modal: HTMLDivElement = document.getElementById('modal-buffers') as HTMLDivElement buffer_modal: HTMLDivElement = document.getElementById(
buffer_search: HTMLInputElement = document.getElementById('buffer-search') as HTMLInputElement "modal-buffers"
settings_modal: HTMLDivElement = document.getElementById('modal-settings') as HTMLDivElement ) as HTMLDivElement;
buffer_search: HTMLInputElement = document.getElementById(
"buffer-search"
) as HTMLInputElement;
settings_modal: HTMLDivElement = document.getElementById(
"modal-settings"
) as HTMLDivElement;
// Local script tabs // Local script tabs
local_script_tabs: HTMLDivElement = document.getElementById('local-script-tabs') as HTMLDivElement local_script_tabs: HTMLDivElement = document.getElementById(
"local-script-tabs"
) as HTMLDivElement;
constructor() { constructor() {
// ================================================================================ // ================================================================================
// Loading the universe from local storage // Loading the universe from local storage
// ================================================================================ // ================================================================================
this.selected_universe = "Default"; this.selected_universe = "Default";
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}` this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
this.universes = {...template_universes, ...this.settings.universes} this.universes = { ...template_universes, ...this.settings.universes };
// ================================================================================ // ================================================================================
// Audio context and clock // Audio context and clock
@ -94,110 +111,120 @@ export class Editor {
// CodeMirror Management // CodeMirror Management
// ================================================================================ // ================================================================================
this.userPlugins = this.settings.vimMode ? [] : [vim()];
this.userPlugins = this.settings.vimMode ? [] : [vim()]
this.editorExtensions = [ this.editorExtensions = [
editorSetup, editorSetup,
rangeHighlighting(), rangeHighlighting(),
javascript(), javascript(),
EditorView.updateListener.of((v:ViewUpdate) => { EditorView.updateListener.of((v: ViewUpdate) => {
v v;
// This is the event listener for the editor // This is the event listener for the editor
}), }),
...this.userPlugins ...this.userPlugins,
] ];
let dynamicPlugins = new Compartment; let dynamicPlugins = new Compartment();
this.state = EditorState.create({ this.state = EditorState.create({
extensions: [ extensions: [
...this.editorExtensions, ...this.editorExtensions,
EditorView.lineWrapping, EditorView.lineWrapping,
dynamicPlugins.of(this.userPlugins) dynamicPlugins.of(this.userPlugins),
], ],
doc: this.universes[this.selected_universe].locals[this.local_index].candidate doc: this.universes[this.selected_universe].locals[this.local_index]
}) .candidate,
});
this.view = new EditorView({ this.view = new EditorView({
parent: document.getElementById('editor') as HTMLElement, parent: document.getElementById("editor") as HTMLElement,
state: this.state state: this.state,
}); });
// ================================================================================ // ================================================================================
// Application event listeners // Application event listeners
// ================================================================================ // ================================================================================
document.addEventListener('keydown', (event: KeyboardEvent) => { document.addEventListener("keydown", (event: KeyboardEvent) => {
// TAB should do nothing // TAB should do nothing
if (event.key === 'Tab') { if (event.key === "Tab") {
event.preventDefault(); event.preventDefault();
} }
if (event.ctrlKey && event.key === "s") { if (event.ctrlKey && event.key === "s") {
event.preventDefault(); event.preventDefault();
this.setButtonHighlighting('pause', true) this.setButtonHighlighting("pause", true);
this.clock.pause() this.clock.pause();
} }
if (event.ctrlKey && event.key === "p") { if (event.ctrlKey && event.key === "p") {
event.preventDefault(); event.preventDefault();
this.setButtonHighlighting('play', true) this.setButtonHighlighting("play", true);
this.clock.start() this.clock.start();
} }
// Ctrl + Shift + V: Vim Mode // Ctrl + Shift + V: Vim Mode
if ((event.key === 'v' || event.key === 'V') && event.ctrlKey && event.shiftKey) { if (
this.settings.vimMode = !this.settings.vimMode (event.key === "v" || event.key === "V") &&
event.ctrlKey &&
event.shiftKey
) {
this.settings.vimMode = !this.settings.vimMode;
event.preventDefault(); event.preventDefault();
this.userPlugins = this.settings.vimMode ? [] : [vim()] this.userPlugins = this.settings.vimMode ? [] : [vim()];
this.view.dispatch({ effects: dynamicPlugins.reconfigure(this.userPlugins) }) this.view.dispatch({
effects: dynamicPlugins.reconfigure(this.userPlugins),
});
} }
// Ctrl + Enter or Return: Evaluate the hovered code block // Ctrl + Enter or Return: Evaluate the hovered code block
if ((event.key === 'Enter' || event.key === 'Return') && event.ctrlKey) { if ((event.key === "Enter" || event.key === "Return") && event.ctrlKey) {
event.preventDefault(); event.preventDefault();
// const code = this.getCodeBlock(); // const code = this.getCodeBlock();
this.currentFile.candidate = this.view.state.doc.toString() this.currentFile.candidate = this.view.state.doc.toString();
tryEvaluate(this, this.currentFile) tryEvaluate(this, this.currentFile);
} }
// Shift + Enter or Ctrl + E: evaluate the line // Shift + Enter or Ctrl + E: evaluate the line
if ((event.key === 'Enter' && event.shiftKey) || (event.key === 'e' && event.ctrlKey)) { if (
(event.key === "Enter" && event.shiftKey) ||
(event.key === "e" && event.ctrlKey)
) {
event.preventDefault(); // Prevents the addition of a new line event.preventDefault(); // Prevents the addition of a new line
this.currentFile.candidate = this.view.state.doc.toString() this.currentFile.candidate = this.view.state.doc.toString();
// const code = this.getSelectedLines(); // const code = this.getSelectedLines();
} }
// This is the modal to switch between universes // This is the modal to switch between universes
if (event.ctrlKey && event.key === "b") { if (event.ctrlKey && event.key === "b") {
this.openBuffersModal() this.openBuffersModal();
} }
// This is the modal that opens up the settings // This is the modal that opens up the settings
if (event.shiftKey && event.key === "Escape") { if (event.shiftKey && event.key === "Escape") {
this.openSettingsModal() this.openSettingsModal();
} }
// Switch to local files // Switch to local files
if (event.ctrlKey && event.key === "l") { if (event.ctrlKey && event.key === "l") {
event.preventDefault(); event.preventDefault();
this.changeModeFromInterface('local'); this.changeModeFromInterface("local");
} }
if (event.ctrlKey && event.key === "g") { if (event.ctrlKey && event.key === "g") {
event.preventDefault(); event.preventDefault();
this.changeModeFromInterface('global'); this.changeModeFromInterface("global");
} }
if (event.ctrlKey && event.key === "i") { if (event.ctrlKey && event.key === "i") {
event.preventDefault(); event.preventDefault();
this.changeModeFromInterface('init'); this.changeModeFromInterface("init");
this.changeToLocalBuffer(0) this.changeToLocalBuffer(0);
} }
[112, 113, 114, 115, 116, 117, 118, 119, 120].forEach((keycode, index) => { [112, 113, 114, 115, 116, 117, 118, 119, 120].forEach(
(keycode, index) => {
if (event.keyCode === keycode) { if (event.keyCode === keycode) {
event.preventDefault(); event.preventDefault();
this.changeToLocalBuffer(index) this.changeToLocalBuffer(index);
} }
}) }
);
}); });
// ================================================================================ // ================================================================================
@ -207,223 +234,254 @@ export class Editor {
const tabs = document.querySelectorAll('[id^="tab-"]'); const tabs = document.querySelectorAll('[id^="tab-"]');
// Iterate over the tabs with an index // Iterate over the tabs with an index
for (let i = 0; i < tabs.length; i++) { for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener('click', (event) => { tabs[i].addEventListener("click", (event) => {
// Updating the CSS accordingly // Updating the CSS accordingly
tabs[i].classList.add('bg-orange-300') tabs[i].classList.add("bg-orange-300");
for (let j = 0; j < tabs.length; j++) { 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-orange-300");
} }
this.currentFile.candidate = this.view.state.doc.toString() this.currentFile.candidate = this.view.state.doc.toString();
let tab = event.target as HTMLElement let tab = event.target as HTMLElement;
let tab_id = tab.id.split('-')[1] let tab_id = tab.id.split("-")[1];
this.local_index = parseInt(tab_id) this.local_index = parseInt(tab_id);
this.updateEditorView() this.updateEditorView();
}) });
} }
this.play_buttons.forEach(button => { this.play_buttons.forEach((button) => {
button.addEventListener('click', () => { button.addEventListener("click", () => {
this.setButtonHighlighting('play', true) this.setButtonHighlighting("play", true);
this.clock.start() this.clock.start();
}) });
}) });
this.clear_buttons.forEach(button => { this.clear_buttons.forEach((button) => {
button.addEventListener('click', () => { button.addEventListener("click", () => {
this.setButtonHighlighting('clear', true) this.setButtonHighlighting("clear", true);
if (confirm('Do you want to reset the current universe?')) { if (confirm("Do you want to reset the current universe?")) {
this.universes[this.selected_universe] = template_universe this.universes[this.selected_universe] = template_universe;
this.updateEditorView() this.updateEditorView();
} }
}) });
}) });
this.pause_buttons.forEach((button) => {
button.addEventListener("click", () => {
this.setButtonHighlighting("pause", true);
this.clock.pause();
});
});
this.pause_buttons.forEach(button => { this.local_button.addEventListener("click", () =>
button.addEventListener('click', () => { this.changeModeFromInterface("local")
this.setButtonHighlighting('pause', true) );
this.clock.pause() this.global_button.addEventListener("click", () =>
}) this.changeModeFromInterface("global")
}) );
this.init_button.addEventListener("click", () =>
this.changeModeFromInterface("init")
);
this.local_button.addEventListener('click', () => this.changeModeFromInterface('local')) this.buffer_search.addEventListener("keydown", (event) => {
this.global_button.addEventListener('click', () => this.changeModeFromInterface('global')) this.changeModeFromInterface("local");
this.init_button.addEventListener('click', () => this.changeModeFromInterface('init'))
this.buffer_search.addEventListener('keydown', (event) => {
this.changeModeFromInterface('local')
if (event.key === "Enter") { if (event.key === "Enter") {
let query = this.buffer_search.value let query = this.buffer_search.value;
if (query.length > 2 && query.length < 20) { if (query.length > 2 && query.length < 20) {
this.loadUniverse(query) this.loadUniverse(query);
this.buffer_search.value = "" this.buffer_search.value = "";
this.closeBuffersModal() this.closeBuffersModal();
} }
} }
}) });
} }
get global_buffer() { get global_buffer() {
return this.universes[this.selected_universe.toString()].global return this.universes[this.selected_universe.toString()].global;
} }
get init_buffer() { get init_buffer() {
return this.universes[this.selected_universe.toString()].init return this.universes[this.selected_universe.toString()].init;
} }
changeToLocalBuffer(i: number) { changeToLocalBuffer(i: number) {
// Updating the CSS accordingly // Updating the CSS accordingly
const tabs = document.querySelectorAll('[id^="tab-"]'); const tabs = document.querySelectorAll('[id^="tab-"]');
const tab = tabs[i] as HTMLElement const tab = tabs[i] as HTMLElement;
tab.classList.add('bg-orange-300') tab.classList.add("bg-orange-300");
for (let j = 0; j < tabs.length; j++) { 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-orange-300");
} }
this.currentFile.candidate = this.view.state.doc.toString() this.currentFile.candidate = this.view.state.doc.toString();
let tab_id = tab.id.split('-')[1] let tab_id = tab.id.split("-")[1];
this.local_index = parseInt(tab_id) this.local_index = parseInt(tab_id);
this.updateEditorView() this.updateEditorView();
} }
changeModeFromInterface(mode: 'global' | 'local' | 'init') { changeModeFromInterface(mode: "global" | "local" | "init") {
const interface_buttons: HTMLElement[] = [ const interface_buttons: HTMLElement[] = [
this.local_button, this.local_button,
this.global_button, this.global_button,
this.init_button this.init_button,
] ];
let changeColor = (button: HTMLElement) => { let changeColor = (button: HTMLElement) => {
interface_buttons.forEach(button => { interface_buttons.forEach((button) => {
let svg = button.children[0] as HTMLElement let svg = button.children[0] as HTMLElement;
if (svg.classList.contains('text-orange-300')) { if (svg.classList.contains("text-orange-300")) {
svg.classList.remove('text-orange-300') svg.classList.remove("text-orange-300");
svg.classList.add('text-white') svg.classList.add("text-white");
}
})
button.children[0].classList.add('text-orange-300')
} }
});
button.children[0].classList.add("text-orange-300");
};
if (mode === this.editor_mode) return if (mode === this.editor_mode) return;
switch (mode) { switch (mode) {
case 'local': case "local":
if (this.local_script_tabs.classList.contains('hidden')) { if (this.local_script_tabs.classList.contains("hidden")) {
this.local_script_tabs.classList.remove('hidden') this.local_script_tabs.classList.remove("hidden");
} }
this.currentFile.candidate = this.view.state.doc.toString() this.currentFile.candidate = this.view.state.doc.toString();
changeColor(this.local_button) changeColor(this.local_button);
this.editor_mode = 'local'; this.editor_mode = "local";
break; break;
case 'global': case "global":
if (!this.local_script_tabs.classList.contains('hidden')) { if (!this.local_script_tabs.classList.contains("hidden")) {
this.local_script_tabs.classList.add('hidden') this.local_script_tabs.classList.add("hidden");
} }
this.currentFile.candidate = this.view.state.doc.toString() this.currentFile.candidate = this.view.state.doc.toString();
changeColor(this.global_button) changeColor(this.global_button);
this.editor_mode = 'global'; this.editor_mode = "global";
break; break;
case 'init': case "init":
if (!this.local_script_tabs.classList.contains('hidden')) { if (!this.local_script_tabs.classList.contains("hidden")) {
this.local_script_tabs.classList.add('hidden') this.local_script_tabs.classList.add("hidden");
} }
this.currentFile.candidate = this.view.state.doc.toString() this.currentFile.candidate = this.view.state.doc.toString();
changeColor(this.init_button) changeColor(this.init_button);
this.changeToLocalBuffer(0) this.changeToLocalBuffer(0);
this.editor_mode = 'init'; this.editor_mode = "init";
break; break;
} }
this.updateEditorView(); this.updateEditorView();
} }
setButtonHighlighting(
setButtonHighlighting(button: 'play' | 'pause' | 'clear', highlight: boolean) { button: "play" | "pause" | "clear",
const possible_selectors = [ '[id^="play-button-"]', '[id^="pause-button-"]', '[id^="clear-button-"]', ] highlight: boolean
) {
const possible_selectors = [
'[id^="play-button-"]',
'[id^="pause-button-"]',
'[id^="clear-button-"]',
];
let selector: number; let selector: number;
switch (button) { switch (button) {
case 'play': selector = 0; break; case "play":
case 'pause': selector = 1; break; selector = 0;
case 'clear': selector = 2; break; break;
case "pause":
selector = 1;
break;
case "clear":
selector = 2;
break;
} }
document.querySelectorAll(possible_selectors[selector]).forEach(button => { document
if (highlight) button.children[0].classList.add('fill-orange-300') .querySelectorAll(possible_selectors[selector])
.forEach((button) => {
if (highlight) button.children[0].classList.add("fill-orange-300");
}); });
// All other buttons must lose the highlighting // All other buttons must lose the highlighting
document.querySelectorAll(possible_selectors.filter( document
(_, index) => index != selector).join(',')).forEach(button => { .querySelectorAll(
button.children[0].classList.remove('fill-orange-300') possible_selectors.filter((_, index) => index != selector).join(",")
button.children[0].classList.remove('text-orange-300') )
button.children[0].classList.remove('bg-orange-300') .forEach((button) => {
button.children[0].classList.remove("fill-orange-300");
button.children[0].classList.remove("text-orange-300");
button.children[0].classList.remove("bg-orange-300");
}); });
} }
unfocusPlayButtons() { unfocusPlayButtons() {
document.querySelectorAll('[id^="play-button-"]').forEach(button => { document.querySelectorAll('[id^="play-button-"]').forEach((button) => {
button.children[0].classList.remove('fill-orange-300') button.children[0].classList.remove("fill-orange-300");
}) });
} }
updateEditorView(): void {
updateEditorView():void {
// Remove everything from the editor // Remove everything from the editor
this.view.dispatch({ this.view.dispatch({
changes: { changes: {
from: 0, from: 0,
to: this.view.state.doc.toString().length, to: this.view.state.doc.toString().length,
insert:'' insert: "",
} },
}) });
// Insert something // Insert something
this.view.dispatch({ this.view.dispatch({
changes: { changes: {
from: 0, from: 0,
insert: this.currentFile.candidate insert: this.currentFile.candidate,
} },
}); });
} }
get currentFile(): File { get currentFile(): File {
switch (this.editor_mode) { switch (this.editor_mode) {
case 'global': return this.global_buffer; case "global":
case 'local': return this.universes[this.selected_universe].locals[this.local_index]; return this.global_buffer;
case 'init': return this.init_buffer; case "local":
return this.universes[this.selected_universe].locals[this.local_index];
case "init":
return this.init_buffer;
} }
} }
loadUniverse(universeName: string) { loadUniverse(universeName: string) {
this.currentFile.candidate = this.view.state.doc.toString() this.currentFile.candidate = this.view.state.doc.toString();
let selectedUniverse = universeName.trim() let selectedUniverse = universeName.trim();
if (this.universes[selectedUniverse] === undefined) { if (this.universes[selectedUniverse] === undefined) {
this.universes[selectedUniverse] = template_universe this.universes[selectedUniverse] = template_universe;
} }
this.selected_universe = selectedUniverse this.selected_universe = selectedUniverse;
this.universe_viewer.innerHTML = `Topos: ${selectedUniverse}` this.universe_viewer.innerHTML = `Topos: ${selectedUniverse}`;
// We should also update the editor accordingly // We should also update the editor accordingly
this.view.dispatch({ this.view.dispatch({
changes: { from: 0, to: this.view.state.doc.toString().length, insert:'' } changes: {
}) from: 0,
to: this.view.state.doc.toString().length,
insert: "",
},
});
this.view.dispatch({ this.view.dispatch({
changes: { from: 0, insert: this.currentFile.candidate } changes: { from: 0, insert: this.currentFile.candidate },
}); });
} }
getCodeBlock(): string { getCodeBlock(): string {
// Capture the position of the cursor // Capture the position of the cursor
let cursor = this.view.state.selection.main.head let cursor = this.view.state.selection.main.head;
const state = this.view.state; const state = this.view.state;
const { head } = state.selection.main; const { head } = state.selection.main;
const currentLine = state.doc.lineAt(head); const currentLine = state.doc.lineAt(head);
let startLine = currentLine; let startLine = currentLine;
while (startLine.number > 1 && !/^\s*$/.test(state.doc.line(startLine.number - 1).text)) { while (
startLine.number > 1 &&
!/^\s*$/.test(state.doc.line(startLine.number - 1).text)
) {
startLine = state.doc.line(startLine.number - 1); startLine = state.doc.line(startLine.number - 1);
} }
let endLine = currentLine; let endLine = currentLine;
while ( while (
endLine.number < state.doc.lines && !/^\s*$/.test(state.doc.line(endLine.number + 1).text)) { endLine.number < state.doc.lines &&
!/^\s*$/.test(state.doc.line(endLine.number + 1).text)
) {
endLine = state.doc.line(endLine.number + 1); endLine = state.doc.line(endLine.number + 1);
} }
@ -431,20 +489,27 @@ export class Editor {
highlightSelection(this.view); highlightSelection(this.view);
setTimeout(() => { setTimeout(() => {
unhighlightSelection(this.view) unhighlightSelection(this.view);
this.view.dispatch({selection: {anchor: cursor, head: cursor}}); this.view.dispatch({ selection: { anchor: cursor, head: cursor } });
}, 200); }, 200);
let result_string = state.doc.sliceString(startLine.from, endLine.to); let result_string = state.doc.sliceString(startLine.from, endLine.to);
result_string = result_string.split('\n').map((line, index, lines) => { result_string = result_string
.split("\n")
.map((line, index, lines) => {
const trimmedLine = line.trim(); const trimmedLine = line.trim();
if (index === lines.length - 1 || /^\s/.test(lines[index + 1]) || trimmedLine.startsWith('@')) { if (
index === lines.length - 1 ||
/^\s/.test(lines[index + 1]) ||
trimmedLine.startsWith("@")
) {
return line; return line;
} else { } else {
return line + ';\\'; return line + ";\\";
} }
}).join('\n'); })
return result_string .join("\n");
return result_string;
} }
getSelectedLines = (): string => { getSelectedLines = (): string => {
@ -452,39 +517,45 @@ export class Editor {
const { from, to } = state.selection.main; const { from, to } = state.selection.main;
const fromLine = state.doc.lineAt(from); const fromLine = state.doc.lineAt(from);
const toLine = state.doc.lineAt(to); const toLine = state.doc.lineAt(to);
this.view.dispatch({selection: {anchor: 0 + fromLine.from, head: toLine.to}}); this.view.dispatch({
selection: { anchor: 0 + fromLine.from, head: toLine.to },
});
// Release the selection and get the cursor back to its original position // Release the selection and get the cursor back to its original position
// Blink the text! // Blink the text!
highlightSelection(this.view); highlightSelection(this.view);
setTimeout(() => { setTimeout(() => {
unhighlightSelection(this.view) unhighlightSelection(this.view);
this.view.dispatch({selection: {anchor: from, head: from}}); this.view.dispatch({ selection: { anchor: from, head: from } });
}, 200); }, 200);
return state.doc.sliceString(fromLine.from, toLine.to); return state.doc.sliceString(fromLine.from, toLine.to);
} };
openSettingsModal() { openSettingsModal() {
if (document.getElementById('modal-settings')!.classList.contains('invisible')) { if (
document.getElementById('editor')!.classList.add('invisible') document.getElementById("modal-settings")!.classList.contains("invisible")
document.getElementById('modal-settings')!.classList.remove('invisible') ) {
document.getElementById("editor")!.classList.add("invisible");
document.getElementById("modal-settings")!.classList.remove("invisible");
} else { } else {
this.closeSettingsModal(); this.closeSettingsModal();
} }
} }
closeSettingsModal() { closeSettingsModal() {
document.getElementById('editor')!.classList.remove('invisible') document.getElementById("editor")!.classList.remove("invisible");
document.getElementById('modal-settings')!.classList.add('invisible') document.getElementById("modal-settings")!.classList.add("invisible");
} }
openBuffersModal() { openBuffersModal() {
// If the modal is hidden, unhide it and hide the editor // If the modal is hidden, unhide it and hide the editor
if (document.getElementById('modal-buffers')!.classList.contains('invisible')) { if (
document.getElementById('editor')!.classList.add('invisible') document.getElementById("modal-buffers")!.classList.contains("invisible")
document.getElementById('modal-buffers')!.classList.remove('invisible') ) {
document.getElementById('buffer-search')!.focus() document.getElementById("editor")!.classList.add("invisible");
document.getElementById("modal-buffers")!.classList.remove("invisible");
document.getElementById("buffer-search")!.focus();
} else { } else {
this.closeBuffersModal(); this.closeBuffersModal();
} }
@ -492,39 +563,43 @@ export class Editor {
closeBuffersModal() { closeBuffersModal() {
// @ts-ignore // @ts-ignore
document.getElementById('buffer-search')!.value = '' document.getElementById("buffer-search")!.value = "";
document.getElementById('editor')!.classList.remove('invisible') document.getElementById("editor")!.classList.remove("invisible");
document.getElementById('modal')!.classList.add('invisible') document.getElementById("modal")!.classList.add("invisible");
document.getElementById('modal-buffers')!.classList.add('invisible') document.getElementById("modal-buffers")!.classList.add("invisible");
} }
} }
const app = new Editor() const app = new Editor();
function startClock() { function startClock() {
document.getElementById('editor')!.classList.remove('invisible') document.getElementById("editor")!.classList.remove("invisible");
document.getElementById('modal')!.classList.add('hidden') document.getElementById("modal")!.classList.add("hidden");
document.getElementById('modal-container')!.classList.remove('motion-safe:animate-pulse') document
document.getElementById('start-button')!.removeEventListener('click', startClock); .getElementById("modal-container")!
document.removeEventListener('keydown', startOnEnter) .classList.remove("motion-safe:animate-pulse");
app.clock.start() document
app.view.focus() .getElementById("start-button")!
app.setButtonHighlighting('play', true) .removeEventListener("click", startClock);
document.removeEventListener("keydown", startOnEnter);
app.clock.start();
app.view.focus();
app.setButtonHighlighting("play", true);
} }
function startOnEnter(e: KeyboardEvent) { function startOnEnter(e: KeyboardEvent) {
if (e.code === 'Enter' || e.code === "Space") startClock() if (e.code === "Enter" || e.code === "Space") startClock();
} }
document.addEventListener('keydown', startOnEnter) document.addEventListener("keydown", startOnEnter);
document.getElementById('start-button')!.addEventListener( document.getElementById("start-button")!.addEventListener("click", startClock);
'click', startClock);
// When the user leaves the page, all the universes should be saved in the localStorage // When the user leaves the page, all the universes should be saved in the localStorage
window.addEventListener('beforeunload', () => { window.addEventListener("beforeunload", () => {
event.preventDefault();
event.returnValue = "";
// Iterate over all local files and set the candidate to the committed // Iterate over all local files and set the candidate to the committed
app.currentFile.candidate = app.view.state.doc.toString() app.currentFile.candidate = app.view.state.doc.toString();
app.currentFile.committed = app.view.state.doc.toString() app.currentFile.committed = app.view.state.doc.toString();
app.settings.saveApplicationToLocalStorage(app.universes, app.settings) app.settings.saveApplicationToLocalStorage(app.universes, app.settings);
return null;
}); });