소스 검색

Implement a custom attribute system

master
Fen Dweller 3 년 전
부모
커밋
b950864cba
1개의 변경된 파일206개의 추가작업 그리고 115개의 파일을 삭제
  1. +206
    -115
      macrovision.js

+ 206
- 115
macrovision.js 파일 보기

@@ -109,8 +109,18 @@ function typeOfUnit(unit) {
if (dimsEqual(unit, math.unit(1, "joules"))) {
return "energy"
}

return null;
}

const unitPowers = {
"length": 1,
"area": 2,
"volume": 3,
"mass": 3,
"energy": 3 * (3 / 4)
};

math.createUnit({
ShoeSizeMensUS: {
prefixes: "long",
@@ -1631,6 +1641,42 @@ function createEntityMaker(info, views, sizes, forms) {
return maker;
}

// Sets up the getters for each attribute. This needs to be
// re-run if we add new attributes to an entity, so it's
// broken out from makeEntity.

function defineAttributeGetters(view) {
Object.entries(view.attributes).forEach(([key, val]) => {

if (val.defaultUnit !== undefined) {
view.units[key] = val.defaultUnit;
}
if (view[key] !== undefined) {
return;
}
Object.defineProperty(view, key, {
get: function () {
return math.multiply(
Math.pow(
this.parent.scale,
this.attributes[key].power
),
this.attributes[key].base
);
},
set: function (value) {
const newScale = Math.pow(
math.divide(value, this.attributes[key].base),
1 / this.attributes[key].power
);
this.parent.scale = newScale;
},
});
});
}

// This function serializes and parses its arguments to avoid sharing
// references to a common object. This allows for the objects to be
// safely mutated.
@@ -1679,12 +1725,6 @@ function makeEntity(info, views, sizes, forms = {}) {

view.units = {};

Object.entries(view.attributes).forEach(([key, val]) => {
if (val.defaultUnit !== undefined) {
view.units[key] = val.defaultUnit;
}
});

if (
config.autoMass !== "off" &&
view.attributes.weight === undefined
@@ -1789,26 +1829,7 @@ function makeEntity(info, views, sizes, forms = {}) {
};
}

Object.entries(view.attributes).forEach(([key, val]) => {
Object.defineProperty(view, key, {
get: function () {
return math.multiply(
Math.pow(
this.parent.scale,
this.attributes[key].power
),
this.attributes[key].base
);
},
set: function (value) {
const newScale = Math.pow(
math.divide(value, this.attributes[key].base),
1 / this.attributes[key].power
);
this.parent.scale = newScale;
},
});
});
defineAttributeGetters(view);
});

this.sizes.forEach((size) => {
@@ -2307,118 +2328,188 @@ function configViewOptions(entity, view) {
holder.innerHTML = "";

Object.entries(entity.views[view].attributes).forEach(([key, val]) => {
const label = document.createElement("div");
label.classList.add("options-label");
label.innerText = val.name;
if (val.editing) {
const name = document.createElement("input");
name.placeholder = "Enter name...";
holder.appendChild(name);

holder.addEventListener("keydown", (e) => {
e.stopPropagation();
});

holder.appendChild(label);
const input = document.createElement("input");
input.placeholder = "Enter measurement...";
holder.appendChild(input);

const row = document.createElement("div");
row.classList.add("options-row");
input.addEventListener("keydown", (e) => {
e.stopPropagation();
});

holder.appendChild(row);
const button = document.createElement("button");
button.innerText = "Confirm";
holder.appendChild(button);

const input = document.createElement("input");
input.classList.add("options-field-numeric");
input.id = "options-view-" + key + "-input";
button.addEventListener("click", e => {
let unit;
try {
unit = math.unit(input.value);
} catch {
toast("Invalid unit: " + input.value);
return;
}

const select = document.createElement("select");
select.classList.add("options-field-unit");
select.id = "options-view-" + key + "-select";

Object.entries(unitChoices[val.type]).forEach(([group, entries]) => {
const optGroup = document.createElement("optgroup");
optGroup.label = group;
select.appendChild(optGroup);
entries.forEach((entry) => {
const option = document.createElement("option");
option.innerText = entry;
if (entry == defaultUnits[val.type][config.units]) {
option.selected = true;
const unitType = typeOfUnit(unit);

if (unitType === null) {
toast("Unit must be one of length, area, volume, mass, or energy.");
return;
}
select.appendChild(option);

const power = unitPowers[unitType];

const baseValue = math.multiply(unit, math.pow(1/entity.scale, power));
entity.views[view].attributes[key] = {
name: name.value,
power: power,
type: unitType,
base: baseValue,
};

// since we might have changed unit types, we should
// clear this.
entity.currentView.units[key] = undefined;

defineAttributeGetters(entity.views[view]);

configViewOptions(entity, view);
});
});
} else {
const label = document.createElement("div");
label.classList.add("options-label");
label.innerText = val.name;

input.addEventListener("change", (e) => {
const raw_value = input.value == 0 ? 1 : input.value;
let value;
try {
value = math.evaluate(raw_value).toNumber(select.value);
} catch {
holder.appendChild(label);

const row = document.createElement("div");
row.classList.add("options-row");

holder.appendChild(row);

const input = document.createElement("input");
input.classList.add("options-field-numeric");
input.id = "options-view-" + key + "-input";

const select = document.createElement("select");
select.classList.add("options-field-unit");
select.id = "options-view-" + key + "-select";

Object.entries(unitChoices[val.type]).forEach(([group, entries]) => {
const optGroup = document.createElement("optgroup");
optGroup.label = group;
select.appendChild(optGroup);
entries.forEach((entry) => {
const option = document.createElement("option");
option.innerText = entry;
if (entry == defaultUnits[val.type][config.units]) {
option.selected = true;
}
select.appendChild(option);
});
});

input.addEventListener("change", (e) => {
const raw_value = input.value == 0 ? 1 : input.value;
let value;
try {
value = math.evaluate(input.value);
if (typeof value !== "number") {
toast(
"Invalid input: " +
value.format() +
" can't convert to " +
select.value
);
value = math.evaluate(raw_value).toNumber(select.value);
} catch {
try {
value = math.evaluate(input.value);
if (typeof value !== "number") {
toast(
"Invalid input: " +
value.format() +
" can't convert to " +
select.value
);
value = undefined;
}
} catch {
toast("Invalid input: could not parse: " + input.value);
value = undefined;
}
} catch {
toast("Invalid input: could not parse: " + input.value);
value = undefined;
}
}
if (value === undefined) {
return;
}
input.value = value;
entity.views[view][key] = math.unit(value, select.value);
entity.dirty = true;
if (config.autoFit) {
fitWorld();
if (value === undefined) {
return;
}
input.value = value;
entity.views[view][key] = math.unit(value, select.value);
entity.dirty = true;
if (config.autoFit) {
fitWorld();
} else {
updateSizes(true);
}
updateEntityOptions(entity, view);
updateViewOptions(entity, view, key);
});

input.addEventListener("keydown", (e) => {
e.stopPropagation();
});

if (entity.currentView.units[key]) {
select.value = entity.currentView.units[key];
} else {
updateSizes(true);
entity.currentView.units[key] = select.value;
}
updateEntityOptions(entity, view);
updateViewOptions(entity, view, key);
});

input.addEventListener("keydown", (e) => {
e.stopPropagation();
});
select.dataset.oldUnit = select.value;

if (entity.currentView.units[key]) {
select.value = entity.currentView.units[key];
} else {
entity.currentView.units[key] = select.value;
}
setNumericInput(input, entity.views[view][key].toNumber(select.value));

select.dataset.oldUnit = select.value;
// TODO does this ever cause a change in the world?
select.addEventListener("input", (e) => {
const value = input.value == 0 ? 1 : input.value;
const oldUnit = select.dataset.oldUnit;
entity.views[entity.view][key] = math
.unit(value, oldUnit)
.to(select.value);
entity.dirty = true;
setNumericInput(
input,
entity.views[entity.view][key].toNumber(select.value)
);

setNumericInput(input, entity.views[view][key].toNumber(select.value));
select.dataset.oldUnit = select.value;
entity.views[view].units[key] = select.value;

// TODO does this ever cause a change in the world?
select.addEventListener("input", (e) => {
const value = input.value == 0 ? 1 : input.value;
const oldUnit = select.dataset.oldUnit;
entity.views[entity.view][key] = math
.unit(value, oldUnit)
.to(select.value);
entity.dirty = true;
setNumericInput(
input,
entity.views[entity.view][key].toNumber(select.value)
);
if (config.autoFit) {
fitWorld();
} else {
updateSizes(true);
}

select.dataset.oldUnit = select.value;
entity.views[view].units[key] = select.value;
updateEntityOptions(entity, view);
updateViewOptions(entity, view, key);
});

if (config.autoFit) {
fitWorld();
} else {
updateSizes(true);
}
row.appendChild(input);
row.appendChild(select);
}
});

updateEntityOptions(entity, view);
updateViewOptions(entity, view, key);
});
const customButton = document.createElement("button");
customButton.innerText = "New Attribute";
holder.appendChild(customButton);

row.appendChild(input);
row.appendChild(select);
customButton.addEventListener("click", e => {
entity.currentView.attributes["custom" + (Object.keys(entity.currentView.attributes).length + 1)] = {
name: "Custom",
editing: true
}
configViewOptions(entity, view);
});
}



불러오는 중...
취소
저장