From 177f50bce8af30eb25209cd07915d15faa39052f Mon Sep 17 00:00:00 2001 From: FatCatX Date: Wed, 5 May 2021 01:26:58 -0700 Subject: [PATCH] Added clipboard copy and paste Added copy selection to system clipboard. Added paste blueprint from system clipboard. --- src/js/game/blueprint.js | 7 +++ src/js/game/hud/parts/blueprint_placer.js | 67 ++++++++++++++++++++++- src/js/game/hud/parts/mass_selector.js | 24 ++++++++ src/js/savegame/serializer_internal.js | 20 ++++--- 4 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/js/game/blueprint.js b/src/js/game/blueprint.js index 3aaef831..5c615c3f 100644 --- a/src/js/game/blueprint.js +++ b/src/js/game/blueprint.js @@ -14,6 +14,13 @@ export class Blueprint { this.entities = entities; } + /** + * Returns array of entities + */ + getEntities() { + return this.entities; + } + /** * Returns the layer of this blueprint * @returns {Layer} diff --git a/src/js/game/hud/parts/blueprint_placer.js b/src/js/game/hud/parts/blueprint_placer.js index 54e2e3b7..be831928 100644 --- a/src/js/game/hud/parts/blueprint_placer.js +++ b/src/js/game/hud/parts/blueprint_placer.js @@ -1,16 +1,21 @@ import { DrawParameters } from "../../../core/draw_parameters"; +import { createLogger } from "../../../core/logging"; import { STOP_PROPAGATION } from "../../../core/signal"; import { TrackedState } from "../../../core/tracked_state"; import { makeDiv } from "../../../core/utils"; import { Vector } from "../../../core/vector"; import { SOUNDS } from "../../../platform/sound"; +import { SerializerInternal } from "../../../savegame/serializer_internal"; import { T } from "../../../translations"; import { Blueprint } from "../../blueprint"; import { enumMouseButton } from "../../camera"; import { KEYMAPPINGS } from "../../key_action_mapper"; import { BaseHUDPart } from "../base_hud_part"; +import { Entity } from "../../entity"; import { DynamicDomAttach } from "../dynamic_dom_attach"; +const logger = createLogger("blueprint_placer"); + export class HUDBlueprintPlacer extends BaseHUDPart { createElements(parent) { const blueprintCostShape = this.root.shapeDefinitionMgr.getShapeFromShortKey( @@ -48,6 +53,11 @@ export class HUDBlueprintPlacer extends BaseHUDPart { this.domAttach = new DynamicDomAttach(this.root, this.costDisplayParent); this.trackedCanAfford = new TrackedState(this.onCanAffordChanged, this); + + this.serializer = new SerializerInternal(); + + // TODO: This probably belongs at a higher level + document.addEventListener("paste", this.pasteFromClipboard.bind(this)); } abortPlacement() { @@ -98,7 +108,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart { } /** - * mouse down pre handler + * Mouse down pre handler * @param {Vector} pos * @param {enumMouseButton} button */ @@ -183,7 +193,6 @@ export class HUDBlueprintPlacer extends BaseHUDPart { } /** - * * @param {DrawParameters} parameters */ draw(parameters) { @@ -201,4 +210,58 @@ export class HUDBlueprintPlacer extends BaseHUDPart { const tile = worldPos.toTileSpace(); blueprint.draw(parameters, tile); } + + /** + * @param {ClipboardEvent} event + */ + pasteFromClipboard(event) { + const data = event.clipboardData.getData("Text"); + if (!data) { + return; + } + let json; + try { + json = JSON.parse(data); + } catch (error) { + logger.error("Unable to parse clipboard data:", error.message); + return; + } + if (!verifyBlueprintData(json)) { + logger.error("Invalid clipboard data"); + return; + } + logger.log("Paste blueprint from clipboard"); + const entityArray = []; + for (let i = 0; i < json.length; ++i) { + const serializedEntity = json[i]; + const result = this.serializer.deserializeEntity(this.root, serializedEntity); + if (typeof result == "string") { + logger.error(result); + return; + } + entityArray.push(result); + } + const newBP = new Blueprint(entityArray); + this.currentBlueprint.set(newBP); + } +} + +/** + * Verify data is a valid serialized blueprint + * @param {Array} data + */ +function verifyBlueprintData(data) { + if (typeof data != "object") { + return false; + } + if (!Array.isArray(data)) { + return false; + } + for (let i = 0; i < data.length; ++i) { + const value = data[i]; + if (value.components == undefined || value.components.StaticMapEntity == undefined) { + return false; + } + } + return true; } diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index d73e3be3..4845a09d 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -15,6 +15,7 @@ import { KEYMAPPINGS } from "../../key_action_mapper"; import { THEME } from "../../theme"; import { enumHubGoalRewards } from "../../tutorial_goals"; import { Blueprint } from "../../blueprint"; +import { SerializerInternal } from "../../../savegame/serializer_internal"; const logger = createLogger("hud/mass_selector"); @@ -42,6 +43,11 @@ export class HUDMassSelector extends BaseHUDPart { this.root.hud.signals.selectedPlacementBuildingChanged.add(this.clearSelection, this); this.root.signals.editModeChanged.add(this.clearSelection, this); + + this.serializer = new SerializerInternal(); + + // TODO: This probably belongs at a higher level + document.addEventListener("copy", this.copyToClipboard.bind(this)); } /** @@ -202,6 +208,24 @@ export class HUDMassSelector extends BaseHUDPart { } } + /** + * @param {ClipboardEvent} event + */ + copyToClipboard(event) { + if (this.selectedUids.size == 0) { + return; + } + + const entityUids = Array.from(this.selectedUids); + const blueprint = Blueprint.fromUids(this.root, entityUids); + const serializedBP = this.serializer.serializeEntityArray(blueprint.getEntities()); + const json = JSON.stringify(serializedBP); + event.clipboardData.setData("text/plain", json); + event.preventDefault(); + + logger.log("Copied selection to clipboard"); + } + /** * mouse down pre handler * @param {Vector} pos diff --git a/src/js/savegame/serializer_internal.js b/src/js/savegame/serializer_internal.js index c75cebad..f456f17a 100644 --- a/src/js/savegame/serializer_internal.js +++ b/src/js/savegame/serializer_internal.js @@ -25,21 +25,26 @@ export class SerializerInternal { } /** - * * @param {GameRoot} root * @param {Array} array * @returns {string|void} */ deserializeEntityArray(root, array) { for (let i = 0; i < array.length; ++i) { - this.deserializeEntity(root, array[i]); + const serializedEntity = array[i]; + const result = this.deserializeEntity(root, serializedEntity); + if (typeof result == "string") { + return result; + } + root.entityMgr.registerEntity(result, serializedEntity.uid); + root.map.placeStaticEntity(result); } } /** - * * @param {GameRoot} root * @param {Entity} payload + * @returns {string|Entity} */ deserializeEntity(root, payload) { const staticData = payload.components.StaticMapEntity; @@ -61,10 +66,9 @@ export class SerializerInternal { entity.uid = payload.uid; - this.deserializeComponents(root, entity, payload.components); + const errorStatus = this.deserializeComponents(root, entity, payload.components); - root.entityMgr.registerEntity(entity, payload.uid); - root.map.placeStaticEntity(entity); + return errorStatus || entity; } /////// COMPONENTS //// @@ -89,9 +93,7 @@ export class SerializerInternal { } const errorStatus = entity.components[componentId].deserialize(data[componentId], root); - if (errorStatus) { - return errorStatus; - } + return errorStatus; } } }