ensure all numeric values preserve across session

This commit is contained in:
Rachel Powers
2024-04-20 10:58:42 -07:00
parent e4d42d69a5
commit 29d7a10f47
8 changed files with 131 additions and 78 deletions

View File

@@ -52,7 +52,7 @@ export class App extends BaseElement {
`,
];
version = packageJson.version;
appVersion = packageJson.version;
gitVer = __COMMIT_HASH__;
buildDate = __BUILD_DATE__;
@@ -80,7 +80,7 @@ export class App extends BaseElement {
const root = super.createRenderRoot();
root.addEventListener("app-share-session", this._handleShare.bind(this));
root.addEventListener("app-open-file", this._handleOpenFile.bind(this));
root.addEventListener("app-save-as", this._handleSaveAs.bind(this));
root.addEventListener("app-export", this._handleExport.bind(this));
root.addEventListener("app-save", this._handleSave.bind(this));
return root;
}
@@ -88,7 +88,7 @@ export class App extends BaseElement {
protected render(): HTMLTemplateResult {
return html`
<div class="app-container">
<app-nav></app-nav>
<app-nav appVer=${this.appVersion} gitVer=${this.gitVer} buildDate=${this.buildDate} ></app-nav>
<div class="app-body">
<sl-split-panel
style="--min: 20em; --max: calc(100% - 20em);"
@@ -114,7 +114,7 @@ export class App extends BaseElement {
this.shareDialog.show();
}
_handleSaveAs(_e: Event) {
_handleExport(_e: Event) {
saveFile(window.Editor.editorValue);
}

View File

@@ -8,6 +8,8 @@ import "@shoelace-style/shoelace/dist/components/menu/menu.js";
import "@shoelace-style/shoelace/dist/components/divider/divider.js";
import "@shoelace-style/shoelace/dist/components/menu-item/menu-item.js";
import "@shoelace-style/shoelace/dist/components/dropdown/dropdown.js";
import '@shoelace-style/shoelace/dist/components/relative-time/relative-time.js';
import '@shoelace-style/shoelace/dist/components/tooltip/tooltip.js';
import SlMenuItem from "@shoelace-style/shoelace/dist/components/menu-item/menu-item.js";
@customElement("app-nav")
@@ -59,6 +61,10 @@ export class Nav extends BaseElement {
position: relative;
color: #fff;
}
.navbar-header .version {
color: var(--sl-color-neutral-500);
font-size: var(--sl-font-size-small);
}
.nav > li > a {
color: #fff;
line-height: 20px;
@@ -92,6 +98,10 @@ export class Nav extends BaseElement {
constructor() {
super();
}
@property() gitVer: string;
@property() appVer: string;
@property() buildDate: string;
protected render(): HTMLTemplateResult {
return html`
<nav id="navBar" class="navbar navbar-default">
@@ -105,26 +115,60 @@ export class Nav extends BaseElement {
></sl-icon-button>
<sl-menu class="menu" @sl-select=${this._menuClickHandler} style="z-index: 10">
<sl-menu-item value="share">Share</sl-menu-item>
<sl-menu-item value="openFile">Open File</sl-menu-item>
<sl-menu-item value="save">Save</sl-menu-item>
<sl-menu-item value="saveAs">Save As</sl-menu-item>
<sl-menu-item value="share">
Share
<sl-icon name="share" slot="prefix"></sl-icon>
</sl-menu-item>
<sl-menu-item value="openFile">
Open File
<sl-icon name="folder2-open" slot="prefix"></sl-icon>
</sl-menu-item>
<sl-menu-item value="save">
Save
<sl-icon name="box-arrow-in-down" slot="prefix"></sl-icon>
</sl-menu-item>
<sl-menu-item value="load">
Load
<sl-icon name="box-arrow-up" slot="prefix"></sl-icon>
</sl-menu-item>
<sl-menu-item value="export">
Export current file
<sl-icon name="file-earmark-arrow-up" slot="prefix"></sl-icon>
</sl-menu-item>
<sl-divider></sl-divider>
<sl-menu-item value="editorSettings"
>Editor Settings</sl-menu-item
>
<sl-menu-item value="editorSettings">
Editor Settings
<sl-icon name="sliders2" slot="prefix"></sl-icon>
</sl-menu-item>
<sl-divider></sl-divider>
<sl-menu-item value="keyboardShortcuts"
>Editor Keyboard Shortcuts</sl-menu-item
>
<sl-menu-item value="keyboardShortcuts">
Editor Keyboard Shortcuts
<sl-icon name="command" slot="prefix"></sl-icon>
</sl-menu-item>
<sl-divider></sl-divider>
<sl-menu-item>
Presets
<sl-icon name="code-square" slot="prefix"></sl-icon>
<sl-menu slot="submenu">
<sl-menu-item value="preset-demo">
Demo
</sl-menu-item>
</sl-menu>
</sl-menu-item>
</sl-menu>
</sl-dropdown>
</div>
<div class="nav navbar-nav navbar-header ms-2">
<a class="navbar-brand" aria-current="page" href=""
>Stationeers IC10 Emulator</a
>
<div class="nav navbar-header ms-2 hstack">
<div>
<a class="navbar-brand" aria-current="page" href="">
Stationeers IC10 Emulator
</a>
</div>
<div class="hstack version mt-auto mb-auto">
<small>v${this.appVer}-${this.gitVer}</small>
<small class="ms-2"><sl-relative-time date=${this.buildDate}></sl-relative-time></small>
</div>
</div>
<div class="nav navbar-nav ms-auto d-flex flex-row">
@@ -183,8 +227,11 @@ export class Nav extends BaseElement {
case "save":
this.dispatchEvent(new CustomEvent("app-save", { bubbles: true }));
break;
case "saveAs":
this.dispatchEvent(new CustomEvent("app-save-as", { bubbles: true }));
case "load":
this.dispatchEvent(new CustomEvent("app-load", { bubbles: true }));
break;
case "export":
this.dispatchEvent(new CustomEvent("app-export", { bubbles: true }));
break;
case "editorSettings":
window.Editor.settingDialog.show();
@@ -192,6 +239,8 @@ export class Nav extends BaseElement {
case "keyboardShortcuts":
window.Editor.kbShortcuts.show();
break;
case 'preset-demo':
window.location.hash = "demo";
default:
console.log("Unknown main menu item", item.value);
}

View File

@@ -137,6 +137,9 @@ export class Session extends EventTarget {
setProgramCode(id: number, code: string) {
this._programs.set(id, code);
if (this.app.vm) {
this.app.vm.updateCode();
}
this.save();
}
@@ -184,9 +187,6 @@ export class Session extends EventTarget {
save() {
if (this._save_timeout) clearTimeout(this._save_timeout);
this._save_timeout = setTimeout(() => {
if (this.app.vm) {
this.app.vm.updateCode();
}
this.saveToFragment();
this._save_timeout = undefined;
}, 1000);

View File

@@ -12,21 +12,37 @@ export function docReady(fn: () => void) {
}
}
function isZeroNegative(zero: number) {
const isZero = zero === 0;
const isNegative = 1 / zero === -Infinity;
return isNegative && isZero;
}
function replacer(key: any, value: any) {
if(value instanceof Map) {
export function numberToString(n: number): string {
if (isZeroNegative(n)) return "-0";
return n.toString();
}
export function displayNumber(n: number): string {
return numberToString(n).replace("Infinity", "∞");
}
function replacer(_key: any, value: any) {
if (value instanceof Map) {
return {
dataType: 'Map',
dataType: "Map",
value: Array.from(value.entries()), // or with spread: value: [...value]
};
} else if (Number.isNaN(value)) {
} else if (
typeof value === "number" &&
(!Number.isFinite(value) || Number.isNaN(value) || isZeroNegative(value))
) {
return {
dataType: 'Number',
value: "NaN",
dataType: "Number",
value: numberToString(value),
};
} else if (typeof value === "undefined" ) {
} else if (typeof value === "undefined") {
return {
dataType: 'undefined',
dataType: "undefined",
};
} else {
return value;
@@ -34,12 +50,12 @@ function replacer(key: any, value: any) {
}
function reviver(_key: any, value: any) {
if(typeof value === 'object' && value !== null) {
if (value.dataType === 'Map') {
if (typeof value === "object" && value !== null) {
if (value.dataType === "Map") {
return new Map(value.value);
} else if (value.dataType === 'Number') {
return parseFloat(value.value)
} else if (value.dataType === 'undefined') {
} else if (value.dataType === "Number") {
return parseFloat(value.value);
} else if (value.dataType === "undefined") {
return undefined;
}
}
@@ -51,14 +67,13 @@ export function toJson(value: any): string {
}
export function fromJson(value: string): any {
return JSON.parse(value, reviver)
return JSON.parse(value, reviver);
}
export function structuralEqual(a: any, b: any): boolean {
const _a = JSON.stringify(a, replacer);
const _b = JSON.stringify(b, replacer);
return _a === _b;
}
// probably not needed, fetch() exists now
@@ -173,26 +188,26 @@ export async function openFile(editor: Ace.Editor) {
export function parseNumber(s: string): number {
switch (s.toLowerCase()) {
case 'nan':
case "nan":
return Number.NaN;
case 'pinf':
case "pinf":
return Number.POSITIVE_INFINITY;
case 'ninf':
case "ninf":
return Number.NEGATIVE_INFINITY;
case 'pi':
case "pi":
return 3.141592653589793;
case 'deg2rad':
case "deg2rad":
return 0.0174532923847437;
case 'rad2deg':
case "rad2deg":
return 57.2957801818848;
case 'epsilon':
case "epsilon":
return Number.EPSILON;
}
if (/^%[01]+$/.test(s)) {
return parseInt(s.slice(1), 2)
return parseInt(s.slice(1), 2);
}
if (/^\$[0-9A-Fa-f]+$/.test(s)) {
return parseInt(s.slice(1), 16)
return parseInt(s.slice(1), 16);
}
if (/[a-fA-F]/.test(s)) {
const hex = parseHex(s);
@@ -200,10 +215,11 @@ export function parseNumber(s: string): number {
return hex;
}
}
s.replace("∞", "Infinity");
return parseFloat(s);
}
export function parseHex(h: string) : number {
export function parseHex(h: string): number {
var val = parseInt(h, 16);
if (val.toString(16) === h.toLowerCase()) {
return val;
@@ -214,10 +230,10 @@ export function parseHex(h: string) : number {
export function parseIntWithHexOrBinary(s: string): number {
if (/^%[01]+$/.test(s)) {
return parseInt(s.slice(1), 2)
return parseInt(s.slice(1), 2);
}
if (/^\$[0-9A-Fa-f]+$/.test(s)) {
return parseInt(s.slice(1), 16)
return parseInt(s.slice(1), 16);
}
if (/[a-fA-F]/.test(s)) {
const hex = parseHex(s);

View File

@@ -36,6 +36,7 @@ import "@shoelace-style/shoelace/dist/components/icon/icon.js";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
import {
displayNumber,
parseIntWithHexOrBinary,
parseNumber,
structuralEqual,
@@ -274,7 +275,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
return html` <sl-input
id="${inputIdBase}${name}"
key="${name}"
value="${field.value}"
value="${displayNumber(field.value)}"
size="small"
@sl-change=${this._handleChangeField}
>
@@ -334,8 +335,14 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
<div class="slot-fields">
${fields.map(
([name, field], _index, _fields) => html`
<sl-input id="${inputIdBase}${name}" slotIndex=${slotIndex} key="${name}" value="${field.value}" size="small"
@sl-change=${this._handleChangeSlotField}>
<sl-input
id="${inputIdBase}${name}"
slotIndex=${slotIndex}
key="${name}"
value="${displayNumber(field.value)}"
size="small"
@sl-change=${this._handleChangeSlotField}
>
<span slot="prefix">${name}</span>
<sl-copy-button slot="suffix" from="${inputIdBase}${name}.value"></sl-copy-button>
<span slot="suffix">${field.field_type}</span>
@@ -1023,7 +1030,7 @@ export class VmDeviceTemplate extends BaseElement {
return html`
<sl-input
key="${name}"
value="${field.value}"
value="${displayNumber(field.value)}"
size="small"
@sl-change=${this._handleChangeField}
?disabled=${name === "PrefabHash"}

View File

@@ -137,7 +137,7 @@ class VirtualMachine extends EventTarget {
const attempt = Date.now().toString(16);
const ic = this._ics.get(id);
const prog = progs.get(id);
if (ic && prog) {
if (ic && prog && ic.code !== prog) {
try {
console.time(`CompileProgram_${id}_${attempt}`);
this.ics.get(id)!.setCodeInvalid(progs.get(id)!);

View File

@@ -9,7 +9,7 @@ import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
import "@shoelace-style/shoelace/dist/components/input/input.js";
import { RegisterSpec } from "ic10emu_wasm";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
import { parseNumber } from "../utils";
import { displayNumber, parseNumber } from "../utils";
@customElement("vm-ic-registers")
export class VMICRegisters extends VMActiveICMixin(BaseElement) {
@@ -47,16 +47,6 @@ export class VMICRegisters extends VMActiveICMixin(BaseElement) {
}
protected render() {
// const inputTypeFromVal = (val: number) => { if (val === Number.NEGATIVE_INFINITY || val === Number.POSITIVE_INFINITY || Number.isNaN(val)) { return "text"; } else { return "number"; } };
const displayVal = (val: number) => {
if (Number.POSITIVE_INFINITY === val) {
return "∞";
} else if (Number.NEGATIVE_INFINITY === val) {
return "-∞";
} else {
return val.toString();
}
};
const registerAliases: [string, number][] = (
(
[...(this.aliases ?? [])].filter(
@@ -83,7 +73,7 @@ export class VMICRegisters extends VMActiveICMixin(BaseElement) {
</div>
<sl-input
type="text"
value="${displayVal(val)}"
value="${displayNumber(val)}"
size="small"
class="reg-input"
@sl-change=${this._handleCellChange}

View File

@@ -8,7 +8,7 @@ import "@shoelace-style/shoelace/dist/components/icon/icon.js";
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
import "@shoelace-style/shoelace/dist/components/input/input.js";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
import { parseNumber } from "../utils";
import { displayNumber, parseNumber } from "../utils";
@customElement("vm-ic-stack")
export class VMICStack extends VMActiveICMixin(BaseElement) {
@@ -44,15 +44,6 @@ export class VMICStack extends VMActiveICMixin(BaseElement) {
}
protected render() {
const displayVal = (val: number) => {
if (Number.POSITIVE_INFINITY === val) {
return "∞";
} else if (Number.NEGATIVE_INFINITY === val) {
return "-∞";
} else {
return val.toString();
}
};
const sp = this.registers![16];
return html`
@@ -67,7 +58,7 @@ export class VMICStack extends VMActiveICMixin(BaseElement) {
</div>
<sl-input
type="text"
value="${displayVal(val)}"
value="${displayNumber(val)}"
size="small"
class="stack-input ${sp === index ? "stack-pointer" : ""}"
@sl-change=${this._handleCellChange}