@casual-simulation/aux-common
Version:
Common library for AUX projects
247 lines • 7.58 kB
JavaScript
import { splice } from '../utils';
import { hasValue, convertToString } from '../bots/BotCalculations';
/**
* The name of the property that indicates an object represents a tag edit.
* Uses the Device Control One control character (UTF code 11) at the beginning to help prevent conflicts with normal property names.
* Normally, we would use a Symbol, but symbols are not supported via structure clone which means that we have to use a normal property.
*/
export const TAG_EDIT_NAME = '\u0011tag_edit';
/**
* Creates a tag edit using the given list of operations.
*/
export function edit(version, ...operations) {
return {
[TAG_EDIT_NAME]: true,
version,
operations: [operations],
};
}
/**
* Creates a tag edit using the given list of operations.
*/
export function remoteEdit(version, ...operations) {
const e = edit(version, ...operations);
e.isRemote = true;
return e;
}
/**
* Creates a tag edit using the given list of operations.
*/
export function edits(version, ...operations) {
return {
[TAG_EDIT_NAME]: true,
version,
operations,
};
}
/**
* Creates a tag edit using the given list of operations.
*/
export function remoteEdits(version, ...operations) {
const e = edits(version, ...operations);
e.isRemote = true;
return e;
}
/**
* Creates a new tag edit using the operations from both of the given edits.
* @param first The first edit.
* @param second The second edit.
*/
export function mergeEdits(first, second) {
const result = edits(mergeVersions(first.version, second.version), ...first.operations, ...second.operations);
if (first.isRemote || second.isRemote) {
result.isRemote = true;
}
return result;
}
/**
* Creates a preserve operation that keeps the given number of characters.
* @param count The number of characters to preserve.
*/
export function preserve(count) {
return {
type: 'preserve',
count,
};
}
/**
* Creates a insert operation that inserts the given text.
* @param text The text to insert.
*/
export function insert(text) {
return {
type: 'insert',
text,
};
}
/**
* Creates a delete operation that deletes the text between the given start and end indexes.
* @param count The number of characters to delete.
*/
export function del(count) {
return {
type: 'delete',
count,
};
}
/**
* Determines if the given value is a tag edit.
* @param value The value to check.
*/
export function isTagEdit(value) {
return typeof value === 'object' && value && value[TAG_EDIT_NAME] === true;
}
/**
* Merges the two version vectors, taking the latest timestamp from each.
* @param first The first.
* @param second The second.
*/
export function mergeVersions(first, second) {
let final = {
...first,
};
for (let site in second) {
final[site] = Math.max(final[site] || 0, second[site] || 0);
}
return final;
}
/**
* Applies the given update to the current state and returns the final result.
* @param state The state.
* @param update The update.
*/
export function apply(state, update) {
let updatedState = Object.assign({}, state);
for (let id in update) {
let botUpdate = update[id];
if (!botUpdate) {
delete updatedState[id];
continue;
}
let bot = updatedState[id];
let isNewBot = !(id in state);
if (!bot) {
bot = Object.assign({}, update[id]);
updatedState[id] = bot;
}
else {
bot = Object.assign({}, bot);
updatedState[id] = bot;
}
if (botUpdate.tags) {
bot.tags = {
...bot.tags,
};
for (let tag in botUpdate.tags) {
let val = botUpdate.tags[tag];
if (isTagEdit(val)) {
bot.tags[tag] = applyTagEdit(isNewBot ? '' : bot.tags[tag], val);
}
else {
bot.tags[tag] = val;
}
}
}
if (botUpdate.signatures) {
bot.signatures = Object.assign({}, bot.signatures, botUpdate.signatures);
}
if ('values' in botUpdate) {
bot.values = Object.assign({}, bot.values, botUpdate.values);
}
if (botUpdate.masks) {
bot.masks = Object.assign({}, bot.masks);
for (let space in botUpdate.masks) {
bot.masks[space] = Object.assign({}, bot.masks[space]);
const tags = botUpdate.masks[space];
for (let tag in tags) {
let val = tags[tag];
if (isTagEdit(val)) {
bot.masks[space][tag] = applyTagEdit(isNewBot ? '' : bot.masks[space][tag], val);
}
else {
bot.masks[space][tag] = val;
}
}
}
}
for (let tag in botUpdate.tags) {
if (bot.tags[tag] === null) {
delete bot.tags[tag];
if ('values' in bot) {
delete bot.values[tag];
}
}
}
let copiedSignatures = false;
for (let hash in botUpdate.signatures) {
if (bot.signatures[hash] === null) {
if (bot.signatures === botUpdate.signatures &&
!copiedSignatures) {
copiedSignatures = true;
bot.signatures = {
...botUpdate.signatures,
};
}
delete bot.signatures[hash];
}
}
if (!!bot.signatures && Object.keys(bot.signatures).length <= 0) {
delete bot.signatures;
}
for (let space in botUpdate.masks) {
for (let tag in botUpdate.masks[space]) {
if (bot.masks[space][tag] === null) {
delete bot.masks[space][tag];
}
}
if (Object.keys(bot.masks[space]).length <= 0) {
delete bot.masks[space];
}
}
if (!!bot.masks && Object.keys(bot.masks).length <= 0) {
delete bot.masks;
}
}
return updatedState;
}
/**
* Applies the given tag edit to the given value and returns a value suitable for use in a tag.
* i.e. This converts empty strings to null.
* @param value The value to edit.
* @param edit The edit to apply.
* @returns
*/
export function applyTagEdit(value, edit) {
value = applyEdit(value, edit);
if (hasValue(value)) {
return value;
}
return null;
}
/**
* Applies the edit to the given value and returns the result.
* Unlike applyTagEdit(), this function only edits the value and returns the result.
* As a result,
* @param value The value.
* @param edit The edit that should be applied.
*/
export function applyEdit(value, edit) {
value = convertToString(value);
for (let ops of edit.operations) {
let index = 0;
for (let op of ops) {
if (op.type === 'preserve') {
index += op.count;
}
else if (op.type === 'insert') {
value = splice(value, index, 0, op.text);
index += op.text.length;
}
else {
value = splice(value, index, op.count, '');
}
}
}
return value;
}
//# sourceMappingURL=AuxStateHelpers.js.map