feat: procedure event serialization (#6669)

* feat: add serialization to procedure base event

* feat: add serialization to procedure change return event

* feat: add serialization to procedure create event

* feat: add serialization of the procedure delete event

* feat: add serialization of procedure enable events

* feat: add serialization of procedure parameter create events

* feat: add serialization of the parameter delete event

* feat: add serialization of procedure parameter rename events

* feat: add serialization for procedure rename events
This commit is contained in:
Beka Westberg
2022-12-09 10:30:39 -08:00
committed by GitHub
parent e3c04978f3
commit d6230b2a44
19 changed files with 378 additions and 52 deletions

View File

@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {Abstract as AbstractEvent} from './events_abstract.js';
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
import type {Workspace} from '../workspace.js';
@@ -12,11 +12,26 @@ import type {Workspace} from '../workspace.js';
/**
* The base event for an event associated with a procedure.
*/
export class ProcedureBase extends AbstractEvent {
export abstract class ProcedureBase extends AbstractEvent {
isBlank = false;
constructor(workspace: Workspace, public readonly model: IProcedureModel) {
super();
this.workspaceId = workspace.id;
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureBaseJson {
const json = super.toJson() as ProcedureBaseJson;
json['procedureId'] = this.model.getId();
return json;
}
}
export interface ProcedureBaseJson extends AbstractEventJson {
procedureId: string;
}

View File

@@ -9,7 +9,7 @@ import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {ProcedureBase} from './events_procedure_base.js';
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
import * as eventUtils from './utils.js';
@@ -49,6 +49,37 @@ export class ProcedureChangeReturn extends ProcedureBase {
procedureModel.setReturnTypes(this.oldTypes);
}
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureChangeReturnJson {
const json = super.toJson() as ProcedureChangeReturnJson;
json['oldTypes'] = this.oldTypes;
return json;
}
/**
* Deserializes the JSON event.
*
* @internal
*/
static fromJson(json: ProcedureChangeReturnJson, workspace: Workspace):
ProcedureChangeReturn {
const model = workspace.getProcedureMap().get(json['procedureId']);
if (!model) {
throw new Error(
'Cannot deserialize procedure change return event because the ' +
'target procedure does not exist');
}
return new ProcedureChangeReturn(workspace, model, json['oldTypes']);
}
}
export interface ProcedureChangeReturnJson extends ProcedureBaseJson {
oldTypes: string[]|null;
}
registry.register(

View File

@@ -5,11 +5,12 @@
*/
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
import {ObservableProcedureModel} from '../procedures.js';
import {ObservableParameterModel, ObservableProcedureModel} from '../procedures.js';
import * as registry from '../registry.js';
import {loadProcedure, saveProcedure, State as ProcedureState} from '../serialization/procedures.js';
import type {Workspace} from '../workspace.js';
import {ProcedureBase} from './events_procedure_base.js';
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
import * as eventUtils from './utils.js';
@@ -30,6 +31,7 @@ export class ProcedureCreate extends ProcedureBase {
const procedureModel = procedureMap.get(this.model.getId());
if (forward) {
if (procedureModel) return;
// TODO: This should add the model to the map instead of creating a dupe.
procedureMap.add(new ObservableProcedureModel(
workspace, this.model.getName(), this.model.getId()));
} else {
@@ -37,6 +39,35 @@ export class ProcedureCreate extends ProcedureBase {
procedureMap.delete(this.model.getId());
}
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureCreateJson {
const json = super.toJson() as ProcedureCreateJson;
json['model'] = saveProcedure(this.model);
return json;
}
/**
* Deserializes the JSON event.
*
* @internal
*/
static fromJson(json: ProcedureCreateJson, workspace: Workspace):
ProcedureCreate {
return new ProcedureCreate(
workspace,
loadProcedure(
ObservableProcedureModel, ObservableParameterModel, json['model'],
workspace));
}
}
export interface ProcedureCreateJson extends ProcedureBaseJson {
model: ProcedureState,
}
registry.register(

View File

@@ -10,7 +10,7 @@ import {ObservableProcedureModel} from '../procedures.js';
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {ProcedureBase} from './events_procedure_base.js';
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
import * as eventUtils from './utils.js';
@@ -38,7 +38,34 @@ export class ProcedureDelete extends ProcedureBase {
workspace, this.model.getName(), this.model.getId()));
}
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureDeleteJson {
return super.toJson() as ProcedureDeleteJson;
}
/**
* Deserializes the JSON event.
*
* @internal
*/
static fromJson(json: ProcedureDeleteJson, workspace: Workspace):
ProcedureDelete {
const model = workspace.getProcedureMap().get(json['procedureId']);
if (!model) {
throw new Error(
'Cannot deserialize procedure delete event because the ' +
'target procedure does not exist');
}
return new ProcedureDelete(workspace, model);
}
}
export interface ProcedureDeleteJson extends ProcedureBaseJson {}
registry.register(
registry.Type.EVENT, eventUtils.PROCEDURE_DELETE, ProcedureDelete);

View File

@@ -9,7 +9,7 @@ import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {ProcedureBase} from './events_procedure_base.js';
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
import * as eventUtils from './utils.js';
/**
@@ -43,7 +43,34 @@ export class ProcedureEnable extends ProcedureBase {
procedureModel.setEnabled(this.oldState);
}
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureEnableJson {
return super.toJson() as ProcedureEnableJson;
}
/**
* Deserializes the JSON event.
*
* @internal
*/
static fromJson(json: ProcedureEnableJson, workspace: Workspace):
ProcedureEnable {
const model = workspace.getProcedureMap().get(json['procedureId']);
if (!model) {
throw new Error(
'Cannot deserialize procedure enable event because the ' +
'target procedure does not exist');
}
return new ProcedureEnable(workspace, model);
}
}
export interface ProcedureEnableJson extends ProcedureBaseJson {}
registry.register(
registry.Type.EVENT, eventUtils.PROCEDURE_ENABLE, ProcedureEnable);

View File

@@ -5,10 +5,35 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {ProcedureBase} from './events_procedure_base.js';
import {IParameterModel} from '../interfaces/i_parameter_model.js';
import {IProcedureModel} from '../interfaces/i_procedure_model.js';
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
import type {Workspace} from '../workspace.js';
/**
* The base event for an event associated with a procedure parameter.
*/
export class ProcedureParameterBase extends ProcedureBase {}
export abstract class ProcedureParameterBase extends ProcedureBase {
constructor(
workspace: Workspace, model: IProcedureModel,
public readonly parameter: IParameterModel) {
super(workspace, model);
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureParameterBaseJson {
const json = super.toJson() as ProcedureParameterBaseJson;
json['parameterId'] = this.model.getId();
return json;
}
}
export interface ProcedureParameterBaseJson extends ProcedureBaseJson {
parameterId: string,
}

View File

@@ -8,9 +8,10 @@ import type {IParameterModel} from '../interfaces/i_parameter_model.js';
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
import {ObservableParameterModel} from '../procedures/observable_parameter_model.js';
import * as registry from '../registry.js';
import {loadParameter, ParameterState, saveParameter} from '../serialization/procedures.js';
import type {Workspace} from '../workspace.js';
import {ProcedureParameterBase} from './events_procedure_parameter_base.js';
import {ProcedureParameterBase, ProcedureParameterBaseJson} from './events_procedure_parameter_base.js';
import * as eventUtils from './utils.js';
@@ -27,9 +28,8 @@ export class ProcedureParameterCreate extends ProcedureParameterBase {
*/
constructor(
workspace: Workspace, procedure: IProcedureModel,
public readonly parameter: IParameterModel,
public readonly index: number) {
super(workspace, procedure);
parameter: IParameterModel, public readonly index: number) {
super(workspace, procedure, parameter);
}
run(forward: boolean) {
@@ -44,6 +44,7 @@ export class ProcedureParameterCreate extends ProcedureParameterBase {
const parameterModel = procedureModel.getParameter(this.index);
if (forward) {
if (this.parameterMatches(parameterModel)) return;
// TODO: This should just add the parameter instead of creating a dupe.
procedureModel.insertParameter(
new ObservableParameterModel(
workspace, this.parameter.getName(), this.parameter.getId()),
@@ -57,6 +58,42 @@ export class ProcedureParameterCreate extends ProcedureParameterBase {
parameterMatches(param: IParameterModel) {
return param && param.getId() === this.parameter.getId();
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureParameterCreateJson {
const json = super.toJson() as ProcedureParameterCreateJson;
json['parameter'] = saveParameter(this.parameter);
json['index'] = this.index;
return json;
}
/**
* Deserializes the JSON event.
*
* @internal
*/
static fromJson(json: ProcedureParameterCreateJson, workspace: Workspace):
ProcedureParameterCreate {
const procedure = workspace.getProcedureMap().get(json['procedureId']);
if (!procedure) {
throw new Error(
'Cannot deserialize parameter create event because the ' +
'target procedure does not exist');
}
return new ProcedureParameterCreate(
workspace, procedure,
loadParameter(ObservableParameterModel, json['parameter'], workspace),
json['index']);
}
}
export interface ProcedureParameterCreateJson extends
ProcedureParameterBaseJson {
parameter: ParameterState, index: number,
}
registry.register(

View File

@@ -10,7 +10,7 @@ import {ObservableParameterModel} from '../procedures/observable_parameter_model
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {ProcedureParameterBase} from './events_procedure_parameter_base.js';
import {ProcedureParameterBase, ProcedureParameterBaseJson} from './events_procedure_parameter_base.js';
import * as eventUtils from './utils.js';
/**
@@ -27,9 +27,8 @@ export class ProcedureParameterDelete extends ProcedureParameterBase {
*/
constructor(
workspace: Workspace, procedure: IProcedureModel,
public readonly parameter: IParameterModel,
public readonly index: number) {
super(workspace, procedure);
parameter: IParameterModel, public readonly index: number) {
super(workspace, procedure, parameter);
}
run(forward: boolean) {
@@ -47,6 +46,7 @@ export class ProcedureParameterDelete extends ProcedureParameterBase {
procedureModel.deleteParameter(this.index);
} else {
if (this.parameterMatches(parameterModel)) return;
// TODO: this should just insert the model instead of creating a dupe.
procedureModel.insertParameter(
new ObservableParameterModel(
workspace, this.parameter.getName(), this.parameter.getId()),
@@ -57,6 +57,39 @@ export class ProcedureParameterDelete extends ProcedureParameterBase {
parameterMatches(param: IParameterModel) {
return param && param.getId() === this.parameter.getId();
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureParameterDeleteJson {
const json = super.toJson() as ProcedureParameterDeleteJson;
json['index'] = this.index;
return json;
}
/**
* Deserializes the JSON event.
*
* @internal
*/
static fromJson(json: ProcedureParameterDeleteJson, workspace: Workspace):
ProcedureParameterDelete {
const model = workspace.getProcedureMap().get(json['procedureId']);
if (!model) {
throw new Error(
'Cannot deserialize procedure delete event because the ' +
'target procedure does not exist');
}
const param = model.getParameter(json['index']);
return new ProcedureParameterDelete(workspace, model, param, json['index']);
}
}
export interface ProcedureParameterDeleteJson extends
ProcedureParameterBaseJson {
index: number;
}
registry.register(

View File

@@ -9,7 +9,7 @@ import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {ProcedureParameterBase} from './events_procedure_parameter_base.js';
import {ProcedureParameterBase, ProcedureParameterBaseJson} from './events_procedure_parameter_base.js';
import * as eventUtils from './utils.js';
/**
@@ -22,23 +22,15 @@ export class ProcedureParameterRename extends ProcedureParameterBase {
constructor(
workspace: Workspace, procedure: IProcedureModel,
public readonly parameter: IParameterModel,
public readonly oldName: string) {
super(workspace, procedure);
parameter: IParameterModel, public readonly oldName: string) {
super(workspace, procedure, parameter);
this.newName = parameter.getName();
}
run(forward: boolean) {
const procedureModel =
this.getEventWorkspace_().getProcedureMap().get(this.model.getId());
if (!procedureModel) {
throw new Error(
'Cannot rename the parameter of a procedure that does not exist ' +
'in the procedure map');
}
const parameterModel = procedureModel.getParameters().find(
(p) => p.getId() === this.parameter.getId());
const parameterModel = findMatchingParameter(
this.getEventWorkspace_(), this.model.getId(), this.parameter.getId());
if (!parameterModel) {
throw new Error(
'Cannot rename a parameter that does not exist ' +
@@ -50,6 +42,59 @@ export class ProcedureParameterRename extends ProcedureParameterBase {
parameterModel.setName(this.oldName);
}
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureParameterRenameJson {
const json = super.toJson() as ProcedureParameterRenameJson;
json['oldName'] = this.oldName;
return json;
}
/**
* Deserializes the JSON event.
*
* @internal
*/
static fromJson(json: ProcedureParameterRenameJson, workspace: Workspace):
ProcedureParameterRename {
const model = workspace.getProcedureMap().get(json['procedureId']);
if (!model) {
throw new Error(
'Cannot deserialize procedure delete event because the ' +
'target procedure does not exist');
}
const param = findMatchingParameter(
workspace, json['procedureId'], json['parameterId']);
if (!param) {
throw new Error(
'Cannot deserialize parameter rename event because the ' +
'target parameter does not exist');
}
return new ProcedureParameterRename(
workspace, model, param, json['oldName']);
}
}
function findMatchingParameter(
workspace: Workspace, modelId: string, paramId: string): IParameterModel|
undefined {
const procedureModel = workspace.getProcedureMap().get(modelId);
if (!procedureModel) {
throw new Error(
'Cannot rename the parameter of a procedure that does not exist ' +
'in the procedure map');
}
return procedureModel.getParameters().find((p) => p.getId() === paramId);
}
export interface ProcedureParameterRenameJson extends
ProcedureParameterBaseJson {
oldName: string;
}
registry.register(

View File

@@ -8,7 +8,7 @@ import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {ProcedureBase} from './events_procedure_base.js';
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
import * as eventUtils from './utils.js';
/**
@@ -41,6 +41,37 @@ export class ProcedureRename extends ProcedureBase {
procedureModel.setName(this.oldName);
}
}
/**
* Encode the event as JSON.
*
* @returns JSON representation.
*/
toJson(): ProcedureRenameJson {
const json = super.toJson() as ProcedureRenameJson;
json['oldName'] = this.oldName;
return json;
}
/**
* Deserializes the JSON event.
*
* @internal
*/
static fromJson(json: ProcedureRenameJson, workspace: Workspace):
ProcedureRename {
const model = workspace.getProcedureMap().get(json['procedureId']);
if (!model) {
throw new Error(
'Cannot deserialize procedure rename event because the ' +
'target procedure does not exist');
}
return new ProcedureRename(workspace, model, json['oldName']);
}
}
export interface ProcedureRenameJson extends ProcedureBaseJson {
oldName: string;
}
registry.register(

View File

@@ -18,6 +18,7 @@ import type {Workspace} from '../workspace.js';
* Representation of a procedure data model.
*/
export interface State {
// TODO: This should also handle enabled.
id: string, name: string, returnTypes: string[]|null,
parameters?: ParameterState[],
}
@@ -50,8 +51,12 @@ type ParameterModelConstructor<ParameterModel extends IParameterModel> =
new (workspace: Workspace, name: string, id: string) => ParameterModel;
/** Serializes the given IProcedureModel to JSON. */
function saveProcedure(proc: IProcedureModel): State {
/**
* Serializes the given IProcedureModel to JSON.
*
* @internal
*/
export function saveProcedure(proc: IProcedureModel): State {
const state: State = {
id: proc.getId(),
name: proc.getName(),
@@ -62,8 +67,12 @@ function saveProcedure(proc: IProcedureModel): State {
return state;
}
/** Serializes the given IParameterModel to JSON. */
function saveParameter(param: IParameterModel): ParameterState {
/**
* Serializes the given IParameterModel to JSON.
*
* @internal
*/
export function saveParameter(param: IParameterModel): ParameterState {
const state: ParameterState = {
id: param.getId(),
name: param.getName(),
@@ -73,8 +82,12 @@ function saveParameter(param: IParameterModel): ParameterState {
return state;
}
/** Deserializes the given procedure model State from JSON. */
function
/**
* Deserializes the given procedure model State from JSON.
*
* @internal
*/
export function
loadProcedure<ProcedureModel extends IProcedureModel,
ParameterModel extends IParameterModel>(
procedureModelClass: ProcedureModelConstructor<ProcedureModel>,
@@ -90,12 +103,17 @@ loadProcedure<ProcedureModel extends IProcedureModel,
return proc;
}
/** Deserializes the given ParameterState from JSON. */
function loadParameter<ParameterModel extends IParameterModel>(
/**
* Deserializes the given ParameterState from JSON.
*
* @internal
*/
export function loadParameter<ParameterModel extends IParameterModel>(
parameterModelClass: ParameterModelConstructor<ParameterModel>,
state: ParameterState, workspace: Workspace): ParameterModel {
return new parameterModelClass(workspace, state.name, state.id)
.setTypes(state.types || []);
const model = new parameterModelClass(workspace, state.name, state.id);
if (state.types) model.setTypes(state.types);
return model;
}
/** Serializer for saving and loading procedure state. */

View File

@@ -181,10 +181,11 @@ suite('Procedure Change Return Event', function() {
});
});
suite.skip('serialization', function() {
suite('serialization', function() {
test('events round-trip through JSON', function() {
const model = new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name', 'test id');
this.procedureMap.add(model);
const origEvent = new Blockly.Events.ProcedureChangeReturn(
this.workspace, model, NON_DEFAULT_TYPES);

View File

@@ -146,7 +146,7 @@ suite('Procedure Create Event', function() {
});
});
suite.skip('serialization', function() {
suite('serialization', function() {
test('events round-trip through JSON', function() {
const model = new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name', 'test id');

View File

@@ -146,10 +146,11 @@ suite('Procedure Delete Event', function() {
});
});
suite.skip('serialization', function() {
suite('serialization', function() {
test('events round-trip through JSON', function() {
const model = new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name', 'test id');
this.procedureMap.add(model);
const origEvent = new Blockly.Events.ProcedureDelete(this.workspace, model);
const json = origEvent.toJson();

View File

@@ -172,10 +172,11 @@ suite('Procedure Enable Event', function() {
});
});
suite.skip('serialization', function() {
suite('serialization', function() {
test('events round-trip through JSON', function() {
const model = new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name', 'test id');
this.procedureMap.add(model);
const origEvent = new Blockly.Events.ProcedureEnable(this.workspace, model);
const json = origEvent.toJson();

View File

@@ -197,16 +197,16 @@ suite('Procedure Parameter Create Event', function() {
});
});
suite.skip('serialization', function() {
suite('serialization', function() {
test('events round-trip through JSON', function() {
const param = new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test param name', 'test param id');
const model =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name', 'test id')
.insertParameter(param, 0);
this.workspace, 'test name', 'test id');
this.procedureMap.add(model);
const origEvent = new Blockly.Events.ProcedureParameterCreate(
this.workspace, model);
this.workspace, model, param, 0);
const json = origEvent.toJson();
const newEvent = new Blockly.Events.fromJson(json, this.workspace);

View File

@@ -197,7 +197,7 @@ suite('Procedure Parameter Delete Event', function() {
});
});
suite.skip('serialization', function() {
suite('serialization', function() {
test('events round-trip through JSON', function() {
const param = new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test param name', 'test param id');
@@ -205,6 +205,7 @@ suite('Procedure Parameter Delete Event', function() {
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name', 'test id')
.insertParameter(param, 0);
this.procedureMap.add(model);
const origEvent = new Blockly.Events.ProcedureParameterDelete(
this.workspace, model);

View File

@@ -204,7 +204,7 @@ suite('Procedure Parameter Rename Event', function() {
});
});
suite.skip('serialization', function() {
suite('serialization', function() {
test('events round-trip through JSON', function() {
const param = new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test param name', 'test param id');
@@ -212,6 +212,7 @@ suite('Procedure Parameter Rename Event', function() {
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name', 'test id')
.insertParameter(param, 0);
this.procedureMap.add(model);
const origEvent = new Blockly.Events.ProcedureParameterDelete(
this.workspace, model);

View File

@@ -169,10 +169,11 @@ suite('Procedure Rename Event', function() {
});
});
suite.skip('serialization', function() {
suite('serialization', function() {
test('events round-trip through JSON', function() {
const model = new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name', 'test id');
this.procedureMap.add(model);
const origEvent = new Blockly.Events.ProcedureRename(
this.workspace, model, NON_DEFAULT_NAME);