UNPKG

@casual-simulation/aux-common

Version:
247 lines 7.58 kB
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