Renable session saving to fragment (using bson)

Signed-off-by: Rachel <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel
2024-03-23 18:44:16 -07:00
parent 45f24e01b5
commit 99ed5bfe02
7 changed files with 517 additions and 155 deletions

View File

@@ -8,62 +8,6 @@ import { IC10EditorUI } from './ui.js';
import _ace_ext_langue_tools from "ace-code/src/ext/language_tools";
const demoCode = `# Highlighting Demo
# This is a comment
# Hover a define id anywhere to see it's definition
define a_def 10
# Hover HASH("String")'s to see computed crc32
# hover here vvvvvvvvvvvvvvvv
define a_hash HASH("This is a String")
# hover over an alias anywhere in the code
# to see it's definition
alias a_var r0
alias a_device d0
# instructions have Auto Completion,
# numeric logic types are identified on hover
s db 12 0
# ^^
# hover here
# Enums and their values are Known, Hover them!
# vvvvvvvvvvvvvvvvvv
move r2 LogicType.Temperature
# same with constants
# vvvv
move r3 pinf
# Labels are known
main:
l r1 dr15 RatioWater
move r2 100000.001
# Hover Hash Strings of Known prefab names
# to get their documentation
# vvvvvvvvvvvvvvv
move r0 HASH("AccessCardBlack")
beqz r1 test
# -2045627372 is the crc32 hash of a SolarPanel,
# hover it to see the documentation!
# vvvvvvvvvv
move r1 -2045627372
jal test
move r1 $FF
beqz 0 test
move r1 %1000
yield
j main
test:
add r15 r15 1
j ra
`
async function setupLspWorker() {
// Create a web worker
let worker = new Worker(new URL('./lspWorker.js', import.meta.url));
@@ -80,8 +24,6 @@ async function setupLspWorker() {
function IC10Editor(session_id) {
this.mode = new IC10Mode()
this.settings = {
keyboard: "ace",
cursor: "ace",
@@ -104,11 +46,35 @@ function IC10Editor(session_id) {
this.sessions = {};
this.sessions[session_id] = this.editor.getSession();
this.active_session = session_id;
this.editor.session.setValue(demoCode)
this.bindSession(session_id, this.sessions[session_id]);
this.languageProvider = null;
this.ui = new IC10EditorUI(this);
const self = this;
App.session.onLoad((session) => {
const updated_ids = [];
for (const id in session.programs) {
updated_ids.push(id);
self.createOrSetSession(id, session.programs[id]);
}
for (const id in self.sessions) {
if (!updated_ids.includes(id)) {
self.destroySession(id);
}
}
})
App.session.loadFromFragment();
}
IC10Editor.prototype.createOrSetSession = function(session_id, content) {
if (!this.sessions.hasOwnProperty(session_id)) {
this.newSession(session_id);
}
this.sessions[session_id].setValue(content);
}
IC10Editor.prototype.newSession = function(session_id) {
@@ -116,6 +82,7 @@ IC10Editor.prototype.newSession = function(session_id) {
return false;
}
this.sessions[session_id] = ace.createEditSession("", this.mode);
this.bindSession(session_id, this.sessions[session_id]);
}
IC10Editor.prototype.setupLsp = function(lsp_worker) {
@@ -136,17 +103,19 @@ IC10Editor.prototype.setupLsp = function(lsp_worker) {
}
IC10Editor.prototype.activateSession = function (session_id) {
IC10Editor.prototype.activateSession = function(session_id) {
if (!this.sessions.hasOwnProperty(session_id)) {
return false;
}
this.editor.setSession(this.sessions[session_id]);
let options = this.mode.options ?? {};
this.languageProvider.setSessionOptions(this.sessions[session_id], options);
if (this.languageProvider !== null) {
this.languageProvider.setSessionOptions(this.sessions[session_id], options);
}
return true;
}
IC10Editor.prototype.loadEditorSettings = function () {
IC10Editor.prototype.loadEditorSettings = function() {
const saved_settings = window.localStorage.getItem("editorSettings");
if (saved_settings !== null && saved_settings.length > 0) {
try {
@@ -159,7 +128,7 @@ IC10Editor.prototype.loadEditorSettings = function () {
}
}
IC10Editor.prototype.saveEditorSettings = function () {
IC10Editor.prototype.saveEditorSettings = function() {
const toSave = JSON.stringify(this.settings);
window.localStorage.setItem("editorSettings", toSave);
}
@@ -171,9 +140,20 @@ IC10Editor.prototype.destroySession = function(session_id) {
if (!(Object.keys(this.sessions).length > 1)) {
return false;
}
this.sessions[session_id].destroy();
const session = this.sessions[session_id];
delete this.sessions[session_id];
if (this.active_session = session_id) {
this.activateSession(Object.keys(this.sessions)[0]);
}
session.destroy();
return true;
}
IC10Editor.prototype.bindSession = function(session_id, session) {
session.on('change', () => {
var val = session.getValue();
window.App.session.setProgramCode(session_id, val);
});
}
export { IC10Editor, setupLspWorker };

View File

@@ -56,37 +56,16 @@ function IC10EditorUI(ic10editor) {
console.log(self.ic10editor.editor.getOption('keyboardHandler'));
// let sessionChangeTimeout = 0;
// editor.getSession().on('change', () => {
// if (sessionChangeTimeout) clearTimeout(sessionChangeTimeout);
// sessionChangeTimeout = setTimeout(() => {
// var val = editor.getSession().getValue();
// setDocFragment(val);
// sessionChangeTimeout = 0;
// }, 1000);
// });
self.ic10editor.editor.setTheme("ace/theme/one_dark");
ace.config.loadModule("ace/ext/statusbar", function(module) {
const statusBar = new module.StatusBar(self.ic10editor.editor, document.getElementById("statusBar"));
statusBar.updateStatus(self.ic10editor.editor);
})
// ace_ext_keybinding_menu.init(editor);
// editor.setOption("keyboardHandler", "ace/keyboard/vim");
self.ic10editor.editor.setAutoScrollEditorIntoView(true);
// getContentFromFragment(editor, demoCode);
// window.addEventListener('hashchange', (_event) => {
// getContentFromFragment(editor, "");
// });
}
IC10EditorUI.prototype.updateEditorSettings = function () {
IC10EditorUI.prototype.updateEditorSettings = function() {
const settings = this.ic10editor.settings;
const editor = this.ic10editor.editor;
if (settings.keyboard === 'ace') {
@@ -99,7 +78,7 @@ IC10EditorUI.prototype.updateEditorSettings = function () {
editor.setOption('relativeLineNumbers', settings.relativeLineNumbers);
}
IC10EditorUI.prototype.displayEditorSettings = function () {
IC10EditorUI.prototype.displayEditorSettings = function() {
const settings = this.ic10editor.settings;
document.getElementsByName("editorKeybindRadio").forEach((el) => {
el.checked = el.value === settings.keyboard;
@@ -111,7 +90,7 @@ IC10EditorUI.prototype.displayEditorSettings = function () {
document.getElementById("editorSettingsRelativeLineNumbers").checked = settings.relativeLineNumbers;
}
IC10EditorUI.prototype.reCalcEditorSize = function () {
IC10EditorUI.prototype.reCalcEditorSize = function() {
const editor = this.ic10editor.editor;
const navBar = document.getElementById("navBar");
const statusBarContainer = document.getElementById("statusBarContainer");

View File

@@ -1,13 +1,11 @@
import { init } from "ic10emu_wasm";
import { IC10Editor, setupLspWorker } from "./editor";
import { Session } from './session';
const App = {
editor: null,
sessions: [],
languageProvider: null,
editorSettings: {
}
session: new Session()
};
window.App = App;

View File

@@ -1,65 +1,155 @@
import { BSON } from 'bson';
async function setDocFragment(content) {
const obj = JSON.stringify({ sessions: [{ content }] })
const bytes = new TextEncoder().encode(obj);
try {
const c_bytes = await compress(bytes);
const fragment = base64url_encode(c_bytes);
window.history.replaceState(null, "", `#${fragment}`);
} catch (e) {
console.log("Error compressing content fragment:", e);
return;
const demoCode = `# Highlighting Demo
# This is a comment
# Hover a define id anywhere to see it's definition
define a_def 10
# Hover HASH("String")'s to see computed crc32
# hover here vvvvvvvvvvvvvvvv
define a_hash HASH("This is a String")
# hover over an alias anywhere in the code
# to see it's definition
alias a_var r0
alias a_device d0
# instructions have Auto Completion,
# numeric logic types are identified on hover
s db 12 0
# ^^
# hover here
# Enums and their values are Known, Hover them!
# vvvvvvvvvvvvvvvvvv
move r2 LogicType.Temperature
# same with constants
# vvvv
move r3 pinf
# Labels are known
main:
l r1 dr15 RatioWater
move r2 100000.001
# Hover Hash Strings of Known prefab names
# to get their documentation
# vvvvvvvvvvvvvvv
move r0 HASH("AccessCardBlack")
beqz r1 test
# -2045627372 is the crc32 hash of a SolarPanel,
# hover it to see the documentation!
# vvvvvvvvvv
move r1 -2045627372
jal test
move r1 $FF
beqz 0 test
move r1 %1000
yield
j main
test:
add r15 r15 1
j ra
`
class Session {
constructor() {
this._programs = {};
this._save_timeout = 0;
this._onLoadCallbacks = [];
this.loadFromFragment();
const self = this;
window.addEventListener('hashchange', (_event) => {
self.loadFromFragment();
});
}
get programs() {
return this._programs;
}
set programs(programs) {
Object.assign(this._programs, programs);
}
setProgramCode(id, code) {
this._programs[id] = code;
this.save();
}
onLoad(callback) {
this._onLoadCallbacks.push(callback);
}
_fireOnLoad() {
for (const i in this._onLoadCallbacks) {
const callback = this._onLoadCallbacks[i];
callback(this);
}
}
save() {
if (this._save_timeout) clearTimeout(this._save_timeout);
this._save_timeout = setTimeout(() => {
this.saveToFragment();
}, 1000);
}
async saveToFragment() {
const toSave = { programs: this._programs };
const bytes = BSON.serialize(toSave);
try {
const c_bytes = await compress(bytes);
const fragment = base64url_encode(c_bytes);
window.history.replaceState(null, "", `#${fragment}`);
} catch (e) {
console.log("Error compressing content fragment:", e);
return;
}
}
async loadFromFragment() {
const fragment = window.location.hash.slice(1);
if (fragment === "demo") {
this._programs = { 0: demoCode };
this._fireOnLoad();
return;
}
if (fragment.length > 0) {
const c_bytes = base64url_decode(fragment);
const bytes = await decompressFragment(c_bytes);
if (bytes !== null) {
const data = BSON.deserialize(bytes);
try {
this._programs = Object.assign({}, data.programs);
this._fireOnLoad();
return;
} catch (e) {
console.log("Bad session data:", e);
}
}
}
}
}
async function decompressFragment(c_bytes) {
async function decompressFragment(c_bytes) {
try {
const bytes = await decompress(c_bytes);
const content = new TextDecoder().decode(bytes);
return content;
return bytes;
} catch (e) {
console.log("Error decompressing content fragment:", e);
return null;
}
}
function isJsonContent(content) {
try {
const obj = JSON.parse(content);
return [true, obj];
} catch (_) {
return [false, null];
}
}
async function getContentFromFragment() {
const fragment = window.location.hash.slice(1);
if (fragment.length > 0) {
const c_bytes = base64url_decode(fragment);
const data = await decompressFragment(c_bytes);
if (data !== null) {
const [is_json, session] = isJsonContent(data);
if (is_json) {
try {
const content = session.sessions[0].content
editor.getSession().setValue(content);
return;
} catch (e) {
console.log("Bad session data:", e);
}
} else {
editor.getSession().setValue(data);
return;
}
}
}
editor.getSession().setValue(default_content);
}
async function* streamAsyncIterator(stream) {
// Get a lock on the stream
const reader = stream.getReader();
@@ -122,3 +212,5 @@ async function decompress(bytes) {
}
return await concatUintArrays(chunks);
}
export { Session };