Arokip / flutter_diagram_editor

Fllutter diagram editor library
MIT License
116 stars 38 forks source link

Json serialization fot ComponentData class #31

Closed ProZhar closed 2 years ago

ProZhar commented 2 years ago

Hi is it possible to add toJson() method and factory fromJson() to all your models:

ProZhar commented 2 years ago

Or maybe some like this:


For save:
mixin MyDisposePolicy implements DisposePolicy {
  @override
  Map<String, dynamic> disposeDiagramEditor() {
    canvasWriter.saveToJson();
  }
}

For init:
mixin MyInitPolicy implements InitPolicy {
  @override
  initializeDiagramEditor() {
    canvasWriter.loadFromJson(json);
  }
}
Arokip commented 2 years ago

Hello, first of all I want to thank you for using this package. It's a honor to see that somebody uses it.

I've been already thinking about this feature to be implemented in the package. Some day in the future it can be there but I am afraid there can be some yet unknown issues with the dynamic data. It would be really nice if I manage to implement the serialization that is compatible with any already existing diagram editor file format.

However, I am sure you can implement simple json serialization outside the package in your code for now.

Arokip commented 2 years ago

I did not plan to spend time with it today... but it happed 😅 It's not tested much but it should work. You can try it now: diagram_editor 0.1.2.

I would like to hear your feedback then. Thanks.

ProZhar commented 2 years ago

I have already done this too 😅 I just put it here, maybe it will need you. All work fine with all data even canvasState, components, links properties and dynamic data with different types.

  1. mixin JsonPolicy
///Allows you to create and save editor data to json
mixin JsonPolicy on BasePolicySet {
  initializeFromJson(Map<String, dynamic> json) {
    if (json['canvasState'] != null) {
      JsonCanvasStateReader.fromJson(json['canvasState'], this);
    }
    if (json['components'] != null) {
      canvasReader.model.canvasModel.components =
      HashMap<String, ComponentData>.from((json['components'] as Map).map(
              (id, jsonData) => MapEntry(
              id,
              JsonComponentData.fromJson(
                  jsonData, (json) => MyComponentData.fromJson(json)))));
    }
    if (json['links'] != null) {
      canvasReader.model.canvasModel.links = HashMap<String, LinkData>.from(
          (json['links'] as Map).map((id, jsonData) =>
              MapEntry(
                  id,
                  JsonLinkData.fromJson(
                      jsonData, (json) => MyLinkData.fromJson(json)))));
    }

    canvasWriter.state.updateCanvas();
  }

  Map<String, dynamic> toJson() {
    CanvasStateReader canvasStateReader = canvasReader.state;
    CanvasModel canvasModel = canvasReader.model.canvasModel;

    HashMap<String, ComponentData> components =
        canvasModel.components;
    HashMap<String, LinkData> links = canvasModel.links;

    final data = <String, dynamic>{};
    data['canvasState'] = canvasStateReader.toJson();
    data['components'] = components
        .map((id, componentData) => MapEntry(id, componentData.toJson()));
    data['links'] =
        links.map((id, linkData) => MapEntry(id, linkData.toJson()));
    return data;
  }
}

extension JsonCanvasStateReader on CanvasStateReader {
  static fromJson(Map<String, dynamic> json, JsonPolicy jsonPolicy) {
    jsonPolicy.canvasWriter.state.setPosition(JsonOffset.fromJson(json['position']));
    jsonPolicy.canvasWriter.state.setScale(json['scale'] as double);
    jsonPolicy.canvasReader.state.canvasState.mouseScaleSpeed = json['mouseScaleSpeed'] as double;
    jsonPolicy.canvasWriter.state.setMaxScale(json['maxScale'] as double);
    jsonPolicy.canvasWriter.state.setMinScale(json['minScale'] as double);
    jsonPolicy.canvasWriter.state.setCanvasColor(Color(json['color'] as int));
  }

  Map<String, dynamic> toJson() => {
    'position': position.toJson(),
    'scale': scale,
    'mouseScaleSpeed': mouseScaleSpeed,
    'maxScale': maxScale,
    'minScale': minScale,
    'color': color.value,
  };
}

extension JsonOffset on Offset {
  static Offset fromJson(Map<String, dynamic> json) => Offset(
    json['dx'] as double,
    json['dy'] as double,
  );

  Map<String, dynamic> toJson() => {'dx': dx, 'dy': dy};
}

extension JsonSize on Size {
  static Size fromJson(Map<String, dynamic> json) => Size(
    json['width'] as double,
    json['height'] as double,
  );

  Map<String, dynamic> toJson() => {'width': width, 'height': height};
}

extension JsonLinkStyle on LinkStyle {
  static LinkStyle fromJson(Map<String, dynamic> json) => LinkStyle(
    lineType: LineType.values.firstWhere((e) => e.name == json['lineType']),
    arrowType:
    ArrowType.values.firstWhere((e) => e.name == json['arrowType']),
    backArrowType:
    ArrowType.values.firstWhere((e) => e.name == json['backArrowType']),
    arrowSize: json['arrowSize'] as double,
    backArrowSize: json['backArrowSize'] as double,
    lineWidth: json['lineWidth'] as double,
    color: Color(json['color'] as int),
  );

  Map<String, dynamic> toJson() => {
    'lineType': lineType.name,
    'arrowType': arrowType.name,
    'backArrowType': backArrowType.name,
    'arrowSize': arrowSize,
    'backArrowSize': backArrowSize,
    'lineWidth': lineWidth,
    'color': color.value,
  };
}

extension JsonLinkData on LinkData {
  static LinkData fromJson(Map<String, dynamic> json,
      dynamic Function(Map<String, dynamic> json) createCustomLinkData) {
    dynamic customLinkData;
    switch (json['data'].runtimeType) {
      case Null:
        customLinkData = null;
        break;
      case bool:
        customLinkData = json['data'] as bool;
        break;
      case int:
        customLinkData = json['data'] as int;
        break;
      case double:
        customLinkData = json['data'] as double;
        break;
      case String:
        customLinkData = json['data'] as String;
        break;
      default:
        try {
          customLinkData = createCustomLinkData(json['data']);
        } catch (e) {
          throw FlutterError(
              '$e\n->Your custom link data class must implement fromJson(Map<String, dynamic> json) named constructor');
        }
    }
    return LinkData(
      id: json['id'] as String,
      sourceComponentId: json['sourceComponentId'] as String,
      targetComponentId: json['targetComponentId'] as String,
      linkStyle:
      JsonLinkStyle.fromJson(json['linkStyle'] as Map<String, dynamic>),
      linkPoints: (json['linkPoints'] as List<dynamic>)
          .map((e) => JsonOffset.fromJson(e))
          .toList(),
      data: customLinkData,
    );
  }

  Map<String, dynamic> toJson() {
    dynamic customLinkData;
    switch (data.runtimeType) {
      case Null:
      case bool:
      case int:
      case double:
      case String:
        customLinkData = data;
        break;
      default:
        try {
          customLinkData = data.toJson();
        } catch (e) {
          throw FlutterError(
              '$e\n->Your custom link data class must implement toJson() method');
        }
    }

    return <String, dynamic>{
      'id': id,
      'sourceComponentId': sourceComponentId,
      'targetComponentId': targetComponentId,
      'linkStyle': linkStyle.toJson(),
      'linkPoints': linkPoints.map((offset) => offset.toJson()).toList(),
      'areJointsVisible': areJointsVisible,
      'data': customLinkData,
    };
  }
}

extension JsonConnection on Connection {
  static Connection fromJson(Map<String, dynamic> json) {
    return json['type'] == 'ConnectionIn'
        ? ConnectionIn(
      connectionId: json['connectionId'],
      otherComponentId: json['otherComponentId'],
    )
        : ConnectionOut(
      connectionId: json['connectionId'],
      otherComponentId: json['otherComponentId'],
    );
  }

  Map<String, dynamic> toJson() => {
    'type': runtimeType.toString(),
    'connectionId': connectionId,
    'otherComponentId': otherComponentId,
  };
}

extension JsonComponentData on ComponentData {
  static ComponentData fromJson(Map<String, dynamic> json,
      dynamic Function(Map<String, dynamic> json) createCustomComponentData) {
    dynamic customComponentData;
    switch (json['data'].runtimeType) {
      case Null:
        customComponentData = null;
        break;
      case bool:
        customComponentData = json['data'] as bool;
        break;
      case int:
        customComponentData = json['data'] as int;
        break;
      case double:
        customComponentData = json['data'] as double;
        break;
      case String:
        customComponentData = json['data'] as String;
        break;
      default:
        try {
          customComponentData = createCustomComponentData(json['data']);
        } catch (e) {
          throw FlutterError(
              '$e\n->Your custom component data class must implement fromJson(Map<String, dynamic> json) named constructor');
        }
    }
    return ComponentData(
      id: json['id'] as String,
      position: JsonOffset.fromJson(json['position']),
      size: JsonSize.fromJson(json['size']),
      minSize: JsonSize.fromJson(json['minSize']),
      type: json['type'] as String?,
      data: customComponentData,
    )
      ..zOrder = json['zOrder'] as int
      ..parentId = (json['parentId'] as String?)
      ..childrenIds.addAll((json['childrenIds'] as List).map((e) => e as String))
      ..connections.addAll(
          (json['connections'] as List).map((e) => JsonConnection.fromJson(e)));
  }

  Map<String, dynamic> toJson() {
    dynamic customComponentData;
    switch (data.runtimeType) {
      case Null:
      case bool:
      case int:
      case double:
      case String:
        customComponentData = data;
        break;
      default:
        try {
          customComponentData = data.toJson();
        } catch (e) {
          throw FlutterError(
              '$e\n->Your custom component data class must implement toJson() method');
        }
    }

    return <String, dynamic>{
      'id': id,
      'position': position.toJson(),
      'size': size.toJson(),
      'minSize': minSize.toJson(),
      'type': type,
      'zOrder': zOrder,
      'parentId': parentId,
      'childrenIds': childrenIds,
      'connections': connections.map((e) => e.toJson()).toList(),
      'data': customComponentData,
    };
  }
}
  1. implement MyInitPolicy
/// A place where you can init the canvas or your diagram
/// (eg. load an existing diagram).
mixin MyInitPolicy implements InitPolicy, JsonPolicy {
  @override
  initializeDiagramEditor() {
    initializeFromJson(... Your Map<String, dynamic> json put here...);
  }
}
  1. Thats all.

On the weekend I will try a new version and write feedback then. Thanks.

ProZhar commented 2 years ago

In my code version a json look like this, even linkPoints added

{
  "canvasState": {
    "position": {
      "dx": -56.0,
      "dy": 85.0
    },
    "scale": 1.0,
    "mouseScaleSpeed": 0.8,
    "maxScale": 8.0,
    "minScale": 0.1,
    "color": 4292927712
  },
  "components": {
    "d404a631-f15d-48bf-99fc-967013967eac": {
      "id": "d404a631-f15d-48bf-99fc-967013967eac",
      "position": {
        "dx": 418.0,
        "dy": 365.8703703703704
      },
      "size": {
        "width": 270.0,
        "height": 55.0
      },
      "minSize": {
        "width": 40.0,
        "height": 40.0
      },
      "type": "main_actor",
      "zOrder": 3,
      "parentId": null,
      "childrenIds": [],
      "connections": [
        {
          "type": "ConnectionIn",
          "connectionId": "3ce7b335-68a8-4f42-839e-d827ba8c3ea8",
          "otherComponentId": "567f3950-10c0-410c-b539-e8bdbb012a3e"
        },
        {
          "type": "ConnectionOut",
          "connectionId": "787d2824-e17c-4783-a879-98290b57a54d",
          "otherComponentId": "cb779772-7189-4a94-95df-71fc8e0eb91f"
        }
      ],
      "data": {
        "text": "33333"
      }
    },
    "cb779772-7189-4a94-95df-71fc8e0eb91f": {
      "id": "cb779772-7189-4a94-95df-71fc8e0eb91f",
      "position": {
        "dx": 453.0,
        "dy": 19.5
      },
      "size": {
        "width": 270.0,
        "height": 55.0
      },
      "minSize": {
        "width": 40.0,
        "height": 40.0
      },
      "type": "text",
      "zOrder": 1,
      "parentId": null,
      "childrenIds": [],
      "connections": [
        {
          "type": "ConnectionOut",
          "connectionId": "36cc3e98-8635-4475-9b1f-8cc54394ff53",
          "otherComponentId": "567f3950-10c0-410c-b539-e8bdbb012a3e"
        },
        {
          "type": "ConnectionIn",
          "connectionId": "787d2824-e17c-4783-a879-98290b57a54d",
          "otherComponentId": "d404a631-f15d-48bf-99fc-967013967eac"
        }
      ],
      "data": {
        "text": "11111"
      }
    },
    "567f3950-10c0-410c-b539-e8bdbb012a3e": {
      "id": "567f3950-10c0-410c-b539-e8bdbb012a3e",
      "position": {
        "dx": 659.0,
        "dy": 201.68518518518522
      },
      "size": {
        "width": 270.0,
        "height": 55.0
      },
      "minSize": {
        "width": 40.0,
        "height": 40.0
      },
      "type": "actor",
      "zOrder": 2,
      "parentId": null,
      "childrenIds": [],
      "connections": [
        {
          "type": "ConnectionIn",
          "connectionId": "36cc3e98-8635-4475-9b1f-8cc54394ff53",
          "otherComponentId": "cb779772-7189-4a94-95df-71fc8e0eb91f"
        },
        {
          "type": "ConnectionOut",
          "connectionId": "3ce7b335-68a8-4f42-839e-d827ba8c3ea8",
          "otherComponentId": "d404a631-f15d-48bf-99fc-967013967eac"
        }
      ],
      "data": {
        "text": "2222"
      }
    }
  },
  "links": {
    "3ce7b335-68a8-4f42-839e-d827ba8c3ea8": {
      "id": "3ce7b335-68a8-4f42-839e-d827ba8c3ea8",
      "sourceComponentId": "567f3950-10c0-410c-b539-e8bdbb012a3e",
      "targetComponentId": "d404a631-f15d-48bf-99fc-967013967eac",
      "linkStyle": {
        "lineType": "solid",
        "arrowType": "pointedArrow",
        "backArrowType": "none",
        "arrowSize": 5.0,
        "backArrowSize": 5.0,
        "lineWidth": 1.5,
        "color": 4278190080
      },
      "linkPoints": [
        {
          "dx": 753.6339950372209,
          "dy": 256.6851851851852
        },
        {
          "dx": 593.3660049627791,
          "dy": 365.8703703703704
        }
      ],
      "areJointsVisible": false,
      "data": {
        "startLabel": "",
        "endLabel": ""
      }
    },
    "36cc3e98-8635-4475-9b1f-8cc54394ff53": {
      "id": "36cc3e98-8635-4475-9b1f-8cc54394ff53",
      "sourceComponentId": "cb779772-7189-4a94-95df-71fc8e0eb91f",
      "targetComponentId": "567f3950-10c0-410c-b539-e8bdbb012a3e",
      "linkStyle": {
        "lineType": "solid",
        "arrowType": "pointedArrow",
        "backArrowType": "none",
        "arrowSize": 5.0,
        "backArrowSize": 5.0,
        "lineWidth": 1.5,
        "color": 4278190080
      },
      "linkPoints": [
        {
          "dx": 619.0947347021752,
          "dy": 74.5
        },
        {
          "dx": 762.9052652978247,
          "dy": 201.68518518518522
        }
      ],
      "areJointsVisible": false,
      "data": {
        "startLabel": "",
        "endLabel": ""
      }
    },
    "787d2824-e17c-4783-a879-98290b57a54d": {
      "id": "787d2824-e17c-4783-a879-98290b57a54d",
      "sourceComponentId": "d404a631-f15d-48bf-99fc-967013967eac",
      "targetComponentId": "cb779772-7189-4a94-95df-71fc8e0eb91f",
      "linkStyle": {
        "lineType": "solid",
        "arrowType": "pointedArrow",
        "backArrowType": "none",
        "arrowSize": 5.0,
        "backArrowSize": 5.0,
        "lineWidth": 1.5,
        "color": 4278190080
      },
      "linkPoints": [
        {
          "dx": 530.6161470163122,
          "dy": 365.8703703703704
        },
        {
          "dx": 407.0,
          "dy": 214.0
        },
        {
          "dx": 569.0,
          "dy": 217.0
        },
        {
          "dx": 584.9264705882354,
          "dy": 74.5
        }
      ],
      "areJointsVisible": true,
      "data": {
        "startLabel": "",
        "endLabel": ""
      }
    }
  }
}
ProZhar commented 2 years ago

Hi. My feedback - I have added a pull request.