diff --git a/package.json b/package.json index 72a269a..f11d788 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.0.2", + "ajv": "^8.6.2", "ammo.js": "kripken/ammo.js#aab297a4164779c3a9d8dc8d9da26958de3cb778", "case": "^1.6.3", "color": "^3.1.3", diff --git a/src/validators/Color.ts b/src/validators/Color.ts new file mode 100644 index 0000000..21ea8f8 --- /dev/null +++ b/src/validators/Color.ts @@ -0,0 +1,26 @@ +import Ajv, { JSONSchemaType } from "ajv"; +import { Color } from "../helpers/colors"; + +const ajv = new Ajv(); + +export const ColorSchema: JSONSchemaType = { + $id: "https://www.owlbear.rodeo/schemas/color.json", + enum: [ + "black", + "blue", + "darkGray", + "green", + "lightGray", + "orange", + "pink", + "primary", + "purple", + "red", + "teal", + "white", + "yellow", + ], + type: "string", +}; + +export const isColor = ajv.compile(ColorSchema); diff --git a/src/validators/Drawing.ts b/src/validators/Drawing.ts new file mode 100644 index 0000000..dc975db --- /dev/null +++ b/src/validators/Drawing.ts @@ -0,0 +1,225 @@ +import Ajv from "ajv"; +import { Drawing } from "../types/Drawing"; + +import { ColorSchema } from "./Color"; +import { Vector2Schema } from "./Vector2"; + +export const DrawingSchema = { + $id: "https://www.owlbear.rodeo/schemas/drawing.json", + anyOf: [ + { + $ref: "#/definitions/Shape", + }, + { + $ref: "#/definitions/Path", + }, + ], + definitions: { + PointsData: { + properties: { + points: { + items: { + $ref: "vector2.json", + }, + type: "array", + }, + }, + required: ["points"], + type: "object", + }, + RectData: { + properties: { + height: { + type: "number", + }, + width: { + type: "number", + }, + x: { + type: "number", + }, + y: { + type: "number", + }, + }, + required: ["height", "width", "x", "y"], + type: "object", + }, + CircleData: { + properties: { + radius: { + type: "number", + }, + x: { + type: "number", + }, + y: { + type: "number", + }, + }, + required: ["radius", "x", "y"], + type: "object", + }, + BaseDrawing: { + properties: { + blend: { + type: "boolean", + }, + color: { + $ref: "color.json", + }, + id: { + type: "string", + }, + strokeWidth: { + type: "number", + }, + }, + required: ["blend", "color", "id", "strokeWidth"], + type: "object", + }, + BaseShape: { + allOf: [ + { + $ref: "#/definitions/BaseDrawing", + }, + { + properties: { + type: { + enum: ["shape"], + type: "string", + }, + }, + required: ["type"], + type: "object", + }, + ], + }, + Line: { + allOf: [ + { + $ref: "#/definitions/BaseShape", + }, + { + properties: { + data: { + $ref: "#/definitions/PointsData", + }, + shapeType: { + enum: ["line"], + type: "string", + }, + }, + required: ["data", "shapeType"], + type: "object", + }, + ], + }, + Rectangle: { + allOf: [ + { + $ref: "#/definitions/BaseShape", + }, + { + properties: { + data: { + $ref: "#/definitions/RectData", + }, + shapeType: { + enum: ["rectangle"], + type: "string", + }, + }, + required: ["data", "shapeType"], + type: "object", + }, + ], + }, + Circle: { + allOf: [ + { + $ref: "#/definitions/BaseShape", + }, + { + properties: { + data: { + $ref: "#/definitions/CircleData", + }, + shapeType: { + enum: ["circle"], + type: "string", + }, + }, + required: ["data", "shapeType"], + type: "object", + }, + ], + }, + Triangle: { + allOf: [ + { + $ref: "#/definitions/BaseShape", + }, + { + properties: { + data: { + $ref: "#/definitions/PointsData", + }, + shapeType: { + enum: ["triangle"], + type: "string", + }, + }, + required: ["data", "shapeType"], + type: "object", + }, + ], + }, + Shape: { + anyOf: [ + { + $ref: "#/definitions/Line", + }, + { + $ref: "#/definitions/Rectangle", + }, + { + $ref: "#/definitions/Circle", + }, + { + $ref: "#/definitions/Triangle", + }, + ], + }, + Path: { + allOf: [ + { + $ref: "#/definitions/BaseDrawing", + }, + { + properties: { + data: { + $ref: "#/definitions/PointsData", + }, + pathType: { + enum: ["fill", "stroke"], + type: "string", + }, + type: { + enum: ["path"], + type: "string", + }, + }, + required: ["data", "pathType", "type"], + type: "object", + }, + ], + }, + }, +}; + +export const ajv = new Ajv({ + schemas: [DrawingSchema, ColorSchema, Vector2Schema], +}); + +export const isDrawing = ajv.compile(DrawingSchema); diff --git a/src/validators/Fog.ts b/src/validators/Fog.ts new file mode 100644 index 0000000..72f26a5 --- /dev/null +++ b/src/validators/Fog.ts @@ -0,0 +1,59 @@ +import Ajv from "ajv"; +import { Fog } from "../types/Fog"; + +import { Vector2Schema } from "./Vector2"; +import { ColorSchema } from "./Color"; + +export const FogSchema = { + $id: "https://www.owlbear.rodeo/schemas/fog.json", + properties: { + color: { + $ref: "color.json", + }, + data: { + $ref: "#/definitions/FogData", + }, + id: { + type: "string", + }, + strokeWidth: { + type: "number", + }, + type: { + enum: ["fog"], + type: "string", + }, + visible: { + type: "boolean", + }, + }, + required: ["color", "data", "id", "strokeWidth", "type", "visible"], + type: "object", + definitions: { + FogData: { + properties: { + holes: { + items: { + items: { + $ref: "vector2.json", + }, + type: "array", + }, + type: "array", + }, + points: { + items: { + $ref: "vector2.json", + }, + type: "array", + }, + }, + required: ["holes", "points"], + type: "object", + }, + }, +}; + +const ajv = new Ajv({ schemas: [FogSchema, ColorSchema, Vector2Schema] }); + +export const isFog = ajv.compile(FogSchema); diff --git a/src/validators/MapState.ts b/src/validators/MapState.ts new file mode 100644 index 0000000..751d92f --- /dev/null +++ b/src/validators/MapState.ts @@ -0,0 +1,104 @@ +import Ajv from "ajv"; +import { MapState } from "../types/MapState"; + +import { DrawingSchema } from "./Drawing"; +import { FogSchema } from "./Fog"; +import { NoteSchema } from "./Note"; +import { TokenStateSchema } from "./TokenState"; +import { Vector2Schema } from "./Vector2"; +import { ColorSchema } from "./Color"; +import { OutlineSchema } from "./Outline"; + +export const MapStateSchema: any = { + $id: "https://www.owlbear.rodeo/schemas/map-state.json", + properties: { + tokens: { + $ref: "#/definitions/TokenStates", + }, + drawShapes: { + $ref: "#/definitions/DrawingState", + }, + fogShapes: { + $ref: "#/definitions/FogState", + }, + editFlags: { + items: { + enum: ["drawing", "fog", "notes", "tokens"], + type: "string", + }, + type: "array", + }, + notes: { + $ref: "#/definitions/Notes", + }, + mapId: { + type: "string", + }, + }, + required: [ + "drawShapes", + "editFlags", + "fogShapes", + "mapId", + "notes", + "tokens", + ], + type: "object", + definitions: { + TokenStates: { + propertyNames: { + type: "string", + }, + additionalProperties: { + $ref: "token-state.json", + }, + required: [], + type: "object", + }, + DrawingState: { + propertyNames: { + type: "string", + }, + additionalProperties: { + $ref: "drawing.json", + }, + required: [], + type: "object", + }, + FogState: { + propertyNames: { + type: "string", + }, + additionalProperties: { + $ref: "fog.json", + }, + required: [], + type: "object", + }, + Notes: { + propertyNames: { + type: "string", + }, + additionalProperties: { + $ref: "note.json", + }, + required: [], + type: "object", + }, + }, +}; + +export const ajv = new Ajv({ + schemas: [ + MapStateSchema, + DrawingSchema, + FogSchema, + NoteSchema, + TokenStateSchema, + Vector2Schema, + ColorSchema, + OutlineSchema, + ], +}); + +export const isMapState = ajv.compile(MapStateSchema); diff --git a/src/validators/Note.ts b/src/validators/Note.ts new file mode 100644 index 0000000..a55601c --- /dev/null +++ b/src/validators/Note.ts @@ -0,0 +1,62 @@ +import Ajv, { JSONSchemaType } from "ajv"; +import { Note } from "../types/Note"; + +import { Vector2Schema } from "./Vector2"; +import { ColorSchema } from "./Color"; + +export const NoteSchema: JSONSchemaType = { + $id: "https://www.owlbear.rodeo/schemas/note.json", + properties: { + color: { + $ref: "color.json", + }, + id: { + type: "string", + }, + lastModified: { + type: "number", + }, + lastModifiedBy: { + type: "string", + }, + locked: { + type: "boolean", + }, + size: { + type: "number", + }, + text: { + type: "string", + }, + textOnly: { + type: "boolean", + }, + visible: { + type: "boolean", + }, + x: { + type: "number", + }, + y: { + type: "number", + }, + }, + required: [ + "color", + "id", + "lastModified", + "lastModifiedBy", + "locked", + "size", + "text", + "textOnly", + "visible", + "x", + "y", + ], + type: "object", +}; + +const ajv = new Ajv({ schemas: [NoteSchema, ColorSchema, Vector2Schema] }); + +export const isNote = ajv.compile(NoteSchema); diff --git a/src/validators/Outline.ts b/src/validators/Outline.ts new file mode 100644 index 0000000..e137337 --- /dev/null +++ b/src/validators/Outline.ts @@ -0,0 +1,80 @@ +import Ajv from "ajv"; +import { Outline } from "../types/Outline"; + +const ajv = new Ajv(); + +export const OutlineSchema = { + $id: "https://www.owlbear.rodeo/schemas/outline.json", + anyOf: [ + { + $ref: "#/definitions/CircleOutline", + }, + { + $ref: "#/definitions/RectOutline", + }, + { + $ref: "#/definitions/PathOutline", + }, + ], + definitions: { + CircleOutline: { + properties: { + radius: { + type: "number", + }, + type: { + enum: ["circle"], + type: "string", + }, + x: { + type: "number", + }, + y: { + type: "number", + }, + }, + required: ["radius", "type", "x", "y"], + type: "object", + }, + RectOutline: { + properties: { + height: { + type: "number", + }, + type: { + enum: ["rect"], + type: "string", + }, + width: { + type: "number", + }, + x: { + type: "number", + }, + y: { + type: "number", + }, + }, + required: ["height", "type", "width", "x", "y"], + type: "object", + }, + PathOutline: { + properties: { + points: { + items: { + type: "number", + }, + type: "array", + }, + type: { + enum: ["path"], + type: "string", + }, + }, + required: ["points", "type"], + type: "object", + }, + }, +}; + +export const isColor = ajv.compile(OutlineSchema); diff --git a/src/validators/TokenState.ts b/src/validators/TokenState.ts new file mode 100644 index 0000000..d422420 --- /dev/null +++ b/src/validators/TokenState.ts @@ -0,0 +1,147 @@ +import Ajv from "ajv"; +import { TokenState } from "../types/TokenState"; + +import { ColorSchema } from "./Color"; +import { OutlineSchema } from "./Outline"; + +export const TokenStateSchema = { + $id: "https://www.owlbear.rodeo/schemas/token-state.json", + anyOf: [ + { + $ref: "#/definitions/DefaultTokenState", + }, + { + $ref: "#/definitions/FileTokenState", + }, + ], + definitions: { + TokenCategory: { + enum: ["character", "prop", "vehicle"], + type: "string", + }, + BaseTokenState: { + properties: { + category: { + $ref: "#/definitions/TokenCategory", + }, + height: { + type: "number", + }, + id: { + type: "string", + }, + label: { + type: "string", + }, + lastModified: { + type: "number", + }, + lastModifiedBy: { + type: "string", + }, + locked: { + type: "boolean", + }, + outline: { + $ref: "outline.json", + }, + owner: { + type: "string", + }, + rotation: { + type: "number", + }, + size: { + type: "number", + }, + statuses: { + items: { + $ref: "color.json", + }, + type: "array", + }, + tokenId: { + type: "string", + }, + visible: { + type: "boolean", + }, + width: { + type: "number", + }, + x: { + type: "number", + }, + y: { + type: "number", + }, + }, + required: [ + "category", + "height", + "id", + "label", + "lastModified", + "lastModifiedBy", + "locked", + "outline", + "owner", + "rotation", + "size", + "statuses", + "tokenId", + "visible", + "width", + "x", + "y", + ], + type: "object", + }, + DefaultTokenState: { + allOf: [ + { + $ref: "#/definitions/BaseTokenState", + }, + { + properties: { + key: { + type: "string", + }, + type: { + enum: ["default"], + type: "string", + }, + }, + required: ["key", "type"], + type: "object", + }, + ], + }, + FileTokenState: { + allOf: [ + { + $ref: "#/definitions/BaseTokenState", + }, + { + properties: { + file: { + type: "string", + }, + type: { + enum: ["file"], + type: "string", + }, + }, + required: ["file", "type"], + type: "object", + }, + ], + }, + }, +}; + +export const ajv = new Ajv({ + schemas: [TokenStateSchema, ColorSchema, OutlineSchema], +}); + +export const isTokenState = ajv.compile(TokenStateSchema); diff --git a/src/validators/Vector2.ts b/src/validators/Vector2.ts new file mode 100644 index 0000000..40d01cb --- /dev/null +++ b/src/validators/Vector2.ts @@ -0,0 +1,20 @@ +import Ajv, { JSONSchemaType } from "ajv"; +import Vector2 from "../helpers/Vector2"; + +const ajv = new Ajv(); + +export const Vector2Schema: JSONSchemaType = { + $id: "https://www.owlbear.rodeo/schemas/vector2.json", + properties: { + x: { + type: "number", + }, + y: { + type: "number", + }, + }, + required: ["x", "y"], + type: "object", +}; + +export const isColor = ajv.compile(Vector2Schema); diff --git a/yarn.lock b/yarn.lock index 962b452..f686e89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3686,6 +3686,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.6.2: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -8663,6 +8673,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -11759,6 +11774,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"