Small QoL things
This commit is contained in:
22
index.html
22
index.html
@ -30,6 +30,8 @@
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
|
||||
<!-- The header is hidden on smaller devices -->
|
||||
<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">
|
||||
<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" 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">
|
||||
<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">
|
||||
|
||||
<p>This is Topos, an experimental live coding platform.</p>
|
||||
@ -95,7 +103,17 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<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">
|
||||
|
||||
563
src/main.ts
563
src/main.ts
@ -1,81 +1,98 @@
|
||||
import './style.css'
|
||||
import "./style.css";
|
||||
import { EditorView } from "codemirror";
|
||||
import { editorSetup } from './EditorSetup';
|
||||
import { editorSetup } from "./EditorSetup";
|
||||
import { EditorState, Compartment } from "@codemirror/state";
|
||||
import { javascript } from "@codemirror/lang-javascript";
|
||||
import { Clock } from './Clock'
|
||||
import { Clock } from "./Clock";
|
||||
import { vim } from "@replit/codemirror-vim";
|
||||
import { AppSettings } from './AppSettings';
|
||||
import { ViewUpdate } from '@codemirror/view';
|
||||
import { AppSettings } from "./AppSettings";
|
||||
import { ViewUpdate } from "@codemirror/view";
|
||||
import {
|
||||
highlightSelection,
|
||||
unhighlightSelection,
|
||||
rangeHighlighting
|
||||
rangeHighlighting,
|
||||
} from "./highlightSelection";
|
||||
import { UserAPI } from './API';
|
||||
import { Extension } from '@codemirror/state';
|
||||
import { Universes, File, template_universe, template_universes } from './AppSettings';
|
||||
import { tryEvaluate } from './Evaluator';
|
||||
|
||||
|
||||
import { UserAPI } from "./API";
|
||||
import { Extension } from "@codemirror/state";
|
||||
import {
|
||||
Universes,
|
||||
File,
|
||||
template_universe,
|
||||
template_universes,
|
||||
} from "./AppSettings";
|
||||
import { tryEvaluate } from "./Evaluator";
|
||||
|
||||
export class Editor {
|
||||
|
||||
// Data structures for editor text management
|
||||
universes: Universes = template_universes
|
||||
selected_universe: string
|
||||
local_index: number = 1
|
||||
editor_mode: 'global' | 'local' | 'init' = 'local'
|
||||
universes: Universes = template_universes;
|
||||
selected_universe: string;
|
||||
local_index: number = 1;
|
||||
editor_mode: "global" | "local" | "init" = "local";
|
||||
|
||||
settings = new AppSettings()
|
||||
editorExtensions: Extension[] = []
|
||||
userPlugins: Extension[] = []
|
||||
state: EditorState
|
||||
api: UserAPI
|
||||
settings = new AppSettings();
|
||||
editorExtensions: Extension[] = [];
|
||||
userPlugins: Extension[] = [];
|
||||
state: EditorState;
|
||||
api: UserAPI;
|
||||
|
||||
// Audio stuff
|
||||
audioContext: AudioContext
|
||||
view: EditorView
|
||||
clock: Clock
|
||||
manualPlay: boolean = false
|
||||
audioContext: AudioContext;
|
||||
view: EditorView;
|
||||
clock: Clock;
|
||||
manualPlay: boolean = false;
|
||||
|
||||
// Transport elements
|
||||
play_buttons: HTMLButtonElement[] = [
|
||||
document.getElementById('play-button-1') as HTMLButtonElement,
|
||||
document.getElementById('play-button-2') as HTMLButtonElement
|
||||
]
|
||||
document.getElementById("play-button-1") as HTMLButtonElement,
|
||||
document.getElementById("play-button-2") as HTMLButtonElement,
|
||||
];
|
||||
pause_buttons: HTMLButtonElement[] = [
|
||||
document.getElementById('pause-button-1') as HTMLButtonElement,
|
||||
document.getElementById('pause-button-2') as HTMLButtonElement
|
||||
]
|
||||
document.getElementById("pause-button-1") as HTMLButtonElement,
|
||||
document.getElementById("pause-button-2") as HTMLButtonElement,
|
||||
];
|
||||
clear_buttons: HTMLButtonElement[] = [
|
||||
document.getElementById('clear-button-1') as HTMLButtonElement,
|
||||
document.getElementById('clear-button-2') as HTMLButtonElement
|
||||
]
|
||||
document.getElementById("clear-button-1") as HTMLButtonElement,
|
||||
document.getElementById("clear-button-2") as HTMLButtonElement,
|
||||
];
|
||||
|
||||
// Script selection elements
|
||||
local_button: HTMLButtonElement = document.getElementById('local-button') as HTMLButtonElement
|
||||
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
|
||||
local_button: HTMLButtonElement = document.getElementById(
|
||||
"local-button"
|
||||
) as HTMLButtonElement;
|
||||
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: HTMLDivElement = document.getElementById('modal-buffers') as HTMLDivElement
|
||||
buffer_search: HTMLInputElement = document.getElementById('buffer-search') as HTMLInputElement
|
||||
settings_modal: HTMLDivElement = document.getElementById('modal-settings') as HTMLDivElement
|
||||
buffer_modal: HTMLDivElement = document.getElementById(
|
||||
"modal-buffers"
|
||||
) 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: HTMLDivElement = document.getElementById('local-script-tabs') as HTMLDivElement
|
||||
local_script_tabs: HTMLDivElement = document.getElementById(
|
||||
"local-script-tabs"
|
||||
) as HTMLDivElement;
|
||||
|
||||
constructor() {
|
||||
|
||||
// ================================================================================
|
||||
// Loading the universe from local storage
|
||||
// ================================================================================
|
||||
|
||||
this.selected_universe = "Default";
|
||||
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`
|
||||
this.universes = {...template_universes, ...this.settings.universes}
|
||||
this.universe_viewer.innerHTML = `Topos: ${this.selected_universe}`;
|
||||
this.universes = { ...template_universes, ...this.settings.universes };
|
||||
|
||||
// ================================================================================
|
||||
// Audio context and clock
|
||||
@ -94,110 +111,120 @@ export class Editor {
|
||||
// CodeMirror Management
|
||||
// ================================================================================
|
||||
|
||||
|
||||
this.userPlugins = this.settings.vimMode ? [] : [vim()]
|
||||
this.userPlugins = this.settings.vimMode ? [] : [vim()];
|
||||
|
||||
this.editorExtensions = [
|
||||
editorSetup,
|
||||
rangeHighlighting(),
|
||||
javascript(),
|
||||
EditorView.updateListener.of((v:ViewUpdate) => {
|
||||
v
|
||||
EditorView.updateListener.of((v: ViewUpdate) => {
|
||||
v;
|
||||
// This is the event listener for the editor
|
||||
}),
|
||||
...this.userPlugins
|
||||
]
|
||||
...this.userPlugins,
|
||||
];
|
||||
|
||||
let dynamicPlugins = new Compartment;
|
||||
let dynamicPlugins = new Compartment();
|
||||
this.state = EditorState.create({
|
||||
extensions: [
|
||||
...this.editorExtensions,
|
||||
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({
|
||||
parent: document.getElementById('editor') as HTMLElement,
|
||||
state: this.state
|
||||
parent: document.getElementById("editor") as HTMLElement,
|
||||
state: this.state,
|
||||
});
|
||||
|
||||
// ================================================================================
|
||||
// Application event listeners
|
||||
// ================================================================================
|
||||
|
||||
document.addEventListener('keydown', (event: KeyboardEvent) => {
|
||||
|
||||
document.addEventListener("keydown", (event: KeyboardEvent) => {
|
||||
// TAB should do nothing
|
||||
if (event.key === 'Tab') {
|
||||
if (event.key === "Tab") {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.ctrlKey && event.key === "s") {
|
||||
event.preventDefault();
|
||||
this.setButtonHighlighting('pause', true)
|
||||
this.clock.pause()
|
||||
this.setButtonHighlighting("pause", true);
|
||||
this.clock.pause();
|
||||
}
|
||||
if (event.ctrlKey && event.key === "p") {
|
||||
event.preventDefault();
|
||||
this.setButtonHighlighting('play', true)
|
||||
this.clock.start()
|
||||
this.setButtonHighlighting("play", true);
|
||||
this.clock.start();
|
||||
}
|
||||
|
||||
// Ctrl + Shift + V: Vim Mode
|
||||
if ((event.key === 'v' || event.key === 'V') && event.ctrlKey && event.shiftKey) {
|
||||
this.settings.vimMode = !this.settings.vimMode
|
||||
if (
|
||||
(event.key === "v" || event.key === "V") &&
|
||||
event.ctrlKey &&
|
||||
event.shiftKey
|
||||
) {
|
||||
this.settings.vimMode = !this.settings.vimMode;
|
||||
event.preventDefault();
|
||||
this.userPlugins = this.settings.vimMode ? [] : [vim()]
|
||||
this.view.dispatch({ effects: dynamicPlugins.reconfigure(this.userPlugins) })
|
||||
this.userPlugins = this.settings.vimMode ? [] : [vim()];
|
||||
this.view.dispatch({
|
||||
effects: dynamicPlugins.reconfigure(this.userPlugins),
|
||||
});
|
||||
}
|
||||
|
||||
// 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();
|
||||
// const code = this.getCodeBlock();
|
||||
this.currentFile.candidate = this.view.state.doc.toString()
|
||||
tryEvaluate(this, this.currentFile)
|
||||
this.currentFile.candidate = this.view.state.doc.toString();
|
||||
tryEvaluate(this, this.currentFile);
|
||||
}
|
||||
|
||||
// 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
|
||||
this.currentFile.candidate = this.view.state.doc.toString()
|
||||
this.currentFile.candidate = this.view.state.doc.toString();
|
||||
// const code = this.getSelectedLines();
|
||||
}
|
||||
|
||||
// This is the modal to switch between universes
|
||||
if (event.ctrlKey && event.key === "b") {
|
||||
this.openBuffersModal()
|
||||
this.openBuffersModal();
|
||||
}
|
||||
|
||||
// This is the modal that opens up the settings
|
||||
if (event.shiftKey && event.key === "Escape") {
|
||||
this.openSettingsModal()
|
||||
this.openSettingsModal();
|
||||
}
|
||||
|
||||
// Switch to local files
|
||||
if (event.ctrlKey && event.key === "l") {
|
||||
event.preventDefault();
|
||||
this.changeModeFromInterface('local');
|
||||
this.changeModeFromInterface("local");
|
||||
}
|
||||
if (event.ctrlKey && event.key === "g") {
|
||||
event.preventDefault();
|
||||
this.changeModeFromInterface('global');
|
||||
this.changeModeFromInterface("global");
|
||||
}
|
||||
if (event.ctrlKey && event.key === "i") {
|
||||
event.preventDefault();
|
||||
this.changeModeFromInterface('init');
|
||||
this.changeToLocalBuffer(0)
|
||||
this.changeModeFromInterface("init");
|
||||
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) {
|
||||
event.preventDefault();
|
||||
this.changeToLocalBuffer(index)
|
||||
this.changeToLocalBuffer(index);
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// ================================================================================
|
||||
@ -207,223 +234,254 @@ export class Editor {
|
||||
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) => {
|
||||
|
||||
tabs[i].addEventListener("click", (event) => {
|
||||
// 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++) {
|
||||
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_id = tab.id.split('-')[1]
|
||||
this.local_index = parseInt(tab_id)
|
||||
this.updateEditorView()
|
||||
})
|
||||
let tab = event.target as HTMLElement;
|
||||
let tab_id = tab.id.split("-")[1];
|
||||
this.local_index = parseInt(tab_id);
|
||||
this.updateEditorView();
|
||||
});
|
||||
}
|
||||
|
||||
this.play_buttons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
this.setButtonHighlighting('play', true)
|
||||
this.clock.start()
|
||||
})
|
||||
})
|
||||
this.play_buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
this.setButtonHighlighting("play", true);
|
||||
this.clock.start();
|
||||
});
|
||||
});
|
||||
|
||||
this.clear_buttons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
this.setButtonHighlighting('clear', true)
|
||||
if (confirm('Do you want to reset the current universe?')) {
|
||||
this.universes[this.selected_universe] = template_universe
|
||||
this.updateEditorView()
|
||||
this.clear_buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
this.setButtonHighlighting("clear", true);
|
||||
if (confirm("Do you want to reset the current universe?")) {
|
||||
this.universes[this.selected_universe] = template_universe;
|
||||
this.updateEditorView();
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
this.pause_buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
this.setButtonHighlighting("pause", true);
|
||||
this.clock.pause();
|
||||
});
|
||||
});
|
||||
|
||||
this.pause_buttons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
this.setButtonHighlighting('pause', true)
|
||||
this.clock.pause()
|
||||
})
|
||||
})
|
||||
this.local_button.addEventListener("click", () =>
|
||||
this.changeModeFromInterface("local")
|
||||
);
|
||||
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.global_button.addEventListener('click', () => this.changeModeFromInterface('global'))
|
||||
this.init_button.addEventListener('click', () => this.changeModeFromInterface('init'))
|
||||
|
||||
this.buffer_search.addEventListener('keydown', (event) => {
|
||||
this.changeModeFromInterface('local')
|
||||
this.buffer_search.addEventListener("keydown", (event) => {
|
||||
this.changeModeFromInterface("local");
|
||||
if (event.key === "Enter") {
|
||||
let query = this.buffer_search.value
|
||||
let query = this.buffer_search.value;
|
||||
if (query.length > 2 && query.length < 20) {
|
||||
this.loadUniverse(query)
|
||||
this.buffer_search.value = ""
|
||||
this.closeBuffersModal()
|
||||
this.loadUniverse(query);
|
||||
this.buffer_search.value = "";
|
||||
this.closeBuffersModal();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
get global_buffer() {
|
||||
return this.universes[this.selected_universe.toString()].global
|
||||
return this.universes[this.selected_universe.toString()].global;
|
||||
}
|
||||
|
||||
get init_buffer() {
|
||||
return this.universes[this.selected_universe.toString()].init
|
||||
return this.universes[this.selected_universe.toString()].init;
|
||||
}
|
||||
|
||||
changeToLocalBuffer(i: number) {
|
||||
// Updating the CSS accordingly
|
||||
const tabs = document.querySelectorAll('[id^="tab-"]');
|
||||
const tab = tabs[i] as HTMLElement
|
||||
tab.classList.add('bg-orange-300')
|
||||
const tab = tabs[i] as HTMLElement;
|
||||
tab.classList.add("bg-orange-300");
|
||||
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]
|
||||
this.local_index = parseInt(tab_id)
|
||||
this.updateEditorView()
|
||||
let tab_id = tab.id.split("-")[1];
|
||||
this.local_index = parseInt(tab_id);
|
||||
this.updateEditorView();
|
||||
}
|
||||
|
||||
changeModeFromInterface(mode: 'global' | 'local' | 'init') {
|
||||
|
||||
changeModeFromInterface(mode: "global" | "local" | "init") {
|
||||
const interface_buttons: HTMLElement[] = [
|
||||
this.local_button,
|
||||
this.global_button,
|
||||
this.init_button
|
||||
]
|
||||
this.init_button,
|
||||
];
|
||||
|
||||
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')
|
||||
svg.classList.add('text-white')
|
||||
}
|
||||
})
|
||||
button.children[0].classList.add('text-orange-300')
|
||||
interface_buttons.forEach((button) => {
|
||||
let svg = button.children[0] as HTMLElement;
|
||||
if (svg.classList.contains("text-orange-300")) {
|
||||
svg.classList.remove("text-orange-300");
|
||||
svg.classList.add("text-white");
|
||||
}
|
||||
});
|
||||
button.children[0].classList.add("text-orange-300");
|
||||
};
|
||||
|
||||
if (mode === this.editor_mode) return
|
||||
if (mode === this.editor_mode) return;
|
||||
switch (mode) {
|
||||
case 'local':
|
||||
if (this.local_script_tabs.classList.contains('hidden')) {
|
||||
this.local_script_tabs.classList.remove('hidden')
|
||||
case "local":
|
||||
if (this.local_script_tabs.classList.contains("hidden")) {
|
||||
this.local_script_tabs.classList.remove("hidden");
|
||||
}
|
||||
this.currentFile.candidate = this.view.state.doc.toString()
|
||||
changeColor(this.local_button)
|
||||
this.editor_mode = 'local';
|
||||
this.currentFile.candidate = this.view.state.doc.toString();
|
||||
changeColor(this.local_button);
|
||||
this.editor_mode = "local";
|
||||
break;
|
||||
case 'global':
|
||||
if (!this.local_script_tabs.classList.contains('hidden')) {
|
||||
this.local_script_tabs.classList.add('hidden')
|
||||
case "global":
|
||||
if (!this.local_script_tabs.classList.contains("hidden")) {
|
||||
this.local_script_tabs.classList.add("hidden");
|
||||
}
|
||||
this.currentFile.candidate = this.view.state.doc.toString()
|
||||
changeColor(this.global_button)
|
||||
this.editor_mode = 'global';
|
||||
this.currentFile.candidate = this.view.state.doc.toString();
|
||||
changeColor(this.global_button);
|
||||
this.editor_mode = "global";
|
||||
break;
|
||||
case 'init':
|
||||
if (!this.local_script_tabs.classList.contains('hidden')) {
|
||||
this.local_script_tabs.classList.add('hidden')
|
||||
case "init":
|
||||
if (!this.local_script_tabs.classList.contains("hidden")) {
|
||||
this.local_script_tabs.classList.add("hidden");
|
||||
}
|
||||
this.currentFile.candidate = this.view.state.doc.toString()
|
||||
changeColor(this.init_button)
|
||||
this.changeToLocalBuffer(0)
|
||||
this.editor_mode = 'init';
|
||||
this.currentFile.candidate = this.view.state.doc.toString();
|
||||
changeColor(this.init_button);
|
||||
this.changeToLocalBuffer(0);
|
||||
this.editor_mode = "init";
|
||||
break;
|
||||
}
|
||||
this.updateEditorView();
|
||||
}
|
||||
|
||||
|
||||
setButtonHighlighting(button: 'play' | 'pause' | 'clear', highlight: boolean) {
|
||||
const possible_selectors = [ '[id^="play-button-"]', '[id^="pause-button-"]', '[id^="clear-button-"]', ]
|
||||
setButtonHighlighting(
|
||||
button: "play" | "pause" | "clear",
|
||||
highlight: boolean
|
||||
) {
|
||||
const possible_selectors = [
|
||||
'[id^="play-button-"]',
|
||||
'[id^="pause-button-"]',
|
||||
'[id^="clear-button-"]',
|
||||
];
|
||||
let selector: number;
|
||||
switch (button) {
|
||||
case 'play': selector = 0; break;
|
||||
case 'pause': selector = 1; break;
|
||||
case 'clear': selector = 2; break;
|
||||
case "play":
|
||||
selector = 0;
|
||||
break;
|
||||
case "pause":
|
||||
selector = 1;
|
||||
break;
|
||||
case "clear":
|
||||
selector = 2;
|
||||
break;
|
||||
}
|
||||
document.querySelectorAll(possible_selectors[selector]).forEach(button => {
|
||||
if (highlight) button.children[0].classList.add('fill-orange-300')
|
||||
document
|
||||
.querySelectorAll(possible_selectors[selector])
|
||||
.forEach((button) => {
|
||||
if (highlight) button.children[0].classList.add("fill-orange-300");
|
||||
});
|
||||
// All other buttons must lose the highlighting
|
||||
document.querySelectorAll(possible_selectors.filter(
|
||||
(_, index) => index != selector).join(',')).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')
|
||||
document
|
||||
.querySelectorAll(
|
||||
possible_selectors.filter((_, index) => index != selector).join(",")
|
||||
)
|
||||
.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() {
|
||||
document.querySelectorAll('[id^="play-button-"]').forEach(button => {
|
||||
button.children[0].classList.remove('fill-orange-300')
|
||||
})
|
||||
document.querySelectorAll('[id^="play-button-"]').forEach((button) => {
|
||||
button.children[0].classList.remove("fill-orange-300");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateEditorView():void {
|
||||
updateEditorView(): void {
|
||||
// Remove everything from the editor
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: this.view.state.doc.toString().length,
|
||||
insert:''
|
||||
}
|
||||
})
|
||||
insert: "",
|
||||
},
|
||||
});
|
||||
|
||||
// Insert something
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
insert: this.currentFile.candidate
|
||||
}
|
||||
insert: this.currentFile.candidate,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get currentFile(): File {
|
||||
switch (this.editor_mode) {
|
||||
case 'global': return this.global_buffer;
|
||||
case 'local': return this.universes[this.selected_universe].locals[this.local_index];
|
||||
case 'init': return this.init_buffer;
|
||||
case "global":
|
||||
return this.global_buffer;
|
||||
case "local":
|
||||
return this.universes[this.selected_universe].locals[this.local_index];
|
||||
case "init":
|
||||
return this.init_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
this.universes[selectedUniverse] = template_universe
|
||||
this.universes[selectedUniverse] = template_universe;
|
||||
}
|
||||
this.selected_universe = selectedUniverse
|
||||
this.universe_viewer.innerHTML = `Topos: ${selectedUniverse}`
|
||||
this.selected_universe = selectedUniverse;
|
||||
this.universe_viewer.innerHTML = `Topos: ${selectedUniverse}`;
|
||||
// We should also update the editor accordingly
|
||||
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({
|
||||
changes: { from: 0, insert: this.currentFile.candidate }
|
||||
changes: { from: 0, insert: this.currentFile.candidate },
|
||||
});
|
||||
}
|
||||
|
||||
getCodeBlock(): string {
|
||||
// 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 { head } = state.selection.main;
|
||||
const currentLine = state.doc.lineAt(head);
|
||||
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);
|
||||
}
|
||||
let endLine = currentLine;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -431,20 +489,27 @@ export class Editor {
|
||||
highlightSelection(this.view);
|
||||
|
||||
setTimeout(() => {
|
||||
unhighlightSelection(this.view)
|
||||
this.view.dispatch({selection: {anchor: cursor, head: cursor}});
|
||||
unhighlightSelection(this.view);
|
||||
this.view.dispatch({ selection: { anchor: cursor, head: cursor } });
|
||||
}, 200);
|
||||
|
||||
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();
|
||||
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;
|
||||
} else {
|
||||
return line + ';\\';
|
||||
return line + ";\\";
|
||||
}
|
||||
}).join('\n');
|
||||
return result_string
|
||||
})
|
||||
.join("\n");
|
||||
return result_string;
|
||||
}
|
||||
|
||||
getSelectedLines = (): string => {
|
||||
@ -452,39 +517,45 @@ export class Editor {
|
||||
const { from, to } = state.selection.main;
|
||||
const fromLine = state.doc.lineAt(from);
|
||||
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
|
||||
|
||||
// Blink the text!
|
||||
highlightSelection(this.view);
|
||||
|
||||
setTimeout(() => {
|
||||
unhighlightSelection(this.view)
|
||||
this.view.dispatch({selection: {anchor: from, head: from}});
|
||||
unhighlightSelection(this.view);
|
||||
this.view.dispatch({ selection: { anchor: from, head: from } });
|
||||
}, 200);
|
||||
return state.doc.sliceString(fromLine.from, toLine.to);
|
||||
}
|
||||
};
|
||||
|
||||
openSettingsModal() {
|
||||
if (document.getElementById('modal-settings')!.classList.contains('invisible')) {
|
||||
document.getElementById('editor')!.classList.add('invisible')
|
||||
document.getElementById('modal-settings')!.classList.remove('invisible')
|
||||
if (
|
||||
document.getElementById("modal-settings")!.classList.contains("invisible")
|
||||
) {
|
||||
document.getElementById("editor")!.classList.add("invisible");
|
||||
document.getElementById("modal-settings")!.classList.remove("invisible");
|
||||
} else {
|
||||
this.closeSettingsModal();
|
||||
}
|
||||
}
|
||||
|
||||
closeSettingsModal() {
|
||||
document.getElementById('editor')!.classList.remove('invisible')
|
||||
document.getElementById('modal-settings')!.classList.add('invisible')
|
||||
document.getElementById("editor")!.classList.remove("invisible");
|
||||
document.getElementById("modal-settings")!.classList.add("invisible");
|
||||
}
|
||||
|
||||
openBuffersModal() {
|
||||
// If the modal is hidden, unhide it and hide the editor
|
||||
if (document.getElementById('modal-buffers')!.classList.contains('invisible')) {
|
||||
document.getElementById('editor')!.classList.add('invisible')
|
||||
document.getElementById('modal-buffers')!.classList.remove('invisible')
|
||||
document.getElementById('buffer-search')!.focus()
|
||||
if (
|
||||
document.getElementById("modal-buffers")!.classList.contains("invisible")
|
||||
) {
|
||||
document.getElementById("editor")!.classList.add("invisible");
|
||||
document.getElementById("modal-buffers")!.classList.remove("invisible");
|
||||
document.getElementById("buffer-search")!.focus();
|
||||
} else {
|
||||
this.closeBuffersModal();
|
||||
}
|
||||
@ -492,39 +563,43 @@ export class Editor {
|
||||
|
||||
closeBuffersModal() {
|
||||
// @ts-ignore
|
||||
document.getElementById('buffer-search')!.value = ''
|
||||
document.getElementById('editor')!.classList.remove('invisible')
|
||||
document.getElementById('modal')!.classList.add('invisible')
|
||||
document.getElementById('modal-buffers')!.classList.add('invisible')
|
||||
document.getElementById("buffer-search")!.value = "";
|
||||
document.getElementById("editor")!.classList.remove("invisible");
|
||||
document.getElementById("modal")!.classList.add("invisible");
|
||||
document.getElementById("modal-buffers")!.classList.add("invisible");
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Editor()
|
||||
const app = new Editor();
|
||||
|
||||
function startClock() {
|
||||
document.getElementById('editor')!.classList.remove('invisible')
|
||||
document.getElementById('modal')!.classList.add('hidden')
|
||||
document.getElementById('modal-container')!.classList.remove('motion-safe:animate-pulse')
|
||||
document.getElementById('start-button')!.removeEventListener('click', startClock);
|
||||
document.removeEventListener('keydown', startOnEnter)
|
||||
app.clock.start()
|
||||
app.view.focus()
|
||||
app.setButtonHighlighting('play', true)
|
||||
document.getElementById("editor")!.classList.remove("invisible");
|
||||
document.getElementById("modal")!.classList.add("hidden");
|
||||
document
|
||||
.getElementById("modal-container")!
|
||||
.classList.remove("motion-safe:animate-pulse");
|
||||
document
|
||||
.getElementById("start-button")!
|
||||
.removeEventListener("click", startClock);
|
||||
document.removeEventListener("keydown", startOnEnter);
|
||||
app.clock.start();
|
||||
app.view.focus();
|
||||
app.setButtonHighlighting("play", true);
|
||||
}
|
||||
|
||||
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.getElementById('start-button')!.addEventListener(
|
||||
'click', startClock);
|
||||
document.addEventListener("keydown", startOnEnter);
|
||||
document.getElementById("start-button")!.addEventListener("click", startClock);
|
||||
|
||||
// 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
|
||||
app.currentFile.candidate = app.view.state.doc.toString()
|
||||
app.currentFile.committed = app.view.state.doc.toString()
|
||||
app.settings.saveApplicationToLocalStorage(app.universes, app.settings)
|
||||
return null;
|
||||
app.currentFile.candidate = app.view.state.doc.toString();
|
||||
app.currentFile.committed = app.view.state.doc.toString();
|
||||
app.settings.saveApplicationToLocalStorage(app.universes, app.settings);
|
||||
});
|
||||
Reference in New Issue
Block a user