diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e153a68 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = tab +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{yml,yaml}] +indent_style = space diff --git a/.gitignore b/.gitignore index 828aaed..3983bbc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ _site .jekyll-metadata .obsidian vendor -.DS_Store \ No newline at end of file +node_modules/ +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d0ef5a0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.canvas": "json" + } +} diff --git a/jsoncanvas.schema.json b/jsoncanvas.schema.json new file mode 100644 index 0000000..403b3f7 --- /dev/null +++ b/jsoncanvas.schema.json @@ -0,0 +1,392 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/JsonCanvas", + "definitions": { + "JsonCanvas": { + "type": "object", + "properties": { + "edges": { + "type": "array", + "items": { + "$ref": "#/definitions/JSONCanvasEdge" + }, + "description": "Edges are lines that connect one node to another" + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/definitions/JSONCanvasNode" + }, + "description": "Nodes are objects within the canvas. Nodes may be text, files, links, or groups" + } + }, + "additionalProperties": {} + }, + "JSONCanvasEdge": { + "type": "object", + "properties": { + "color": { + "$ref": "#/definitions/JSONCanvasColor" + }, + "fromNode": { + "type": "string", + "description": "The ID of the node that the edge starts from" + }, + "fromSide": { + "type": "string", + "enum": [ + "top", + "right", + "bottom", + "left" + ], + "description": "The side of the node that the edge connects from" + }, + "id": { + "type": "string", + "description": "The ID for the edge" + }, + "label": { + "type": "string", + "description": "The text label for the edge" + }, + "toEnd": { + "$ref": "#/definitions/JSONCanvasEdgeEnd", + "description": "The rendering style of the end of the edge line" + }, + "toNode": { + "type": "string", + "description": "The ID of the node that the edge ends at" + }, + "toSide": { + "type": "string", + "enum": [ + "top", + "right", + "bottom", + "left" + ], + "description": "The side of the node that the edge connects to" + } + }, + "required": [ + "fromNode", + "id", + "toNode" + ], + "additionalProperties": false + }, + "JSONCanvasColor": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/APresetColor" + } + ] + }, + "APresetColor": { + "type": "string", + "enum": [ + "1", + "2", + "3", + "4", + "5", + "6" + ], + "description": "Six preset colors exist, mapped to the following numbers: 1 red 2 orange 3 yellow 4 green 5 cyan 6 purple" + }, + "JSONCanvasEdgeEnd": { + "type": "string", + "enum": [ + "none", + "arrow" + ], + "description": "The rendering style of the end of the edge line" + }, + "JSONCanvasNode": { + "anyOf": [ + { + "$ref": "#/definitions/JSONCanvasNodeGeneric" + }, + { + "$ref": "#/definitions/JSONCanvasTextNode" + }, + { + "$ref": "#/definitions/JSONCanvasFileNode" + }, + { + "$ref": "#/definitions/JSONCanvasLinkNode" + }, + { + "$ref": "#/definitions/JSONCanvasGroupNode" + } + ] + }, + "JSONCanvasNodeGeneric": { + "type": "object", + "properties": { + "color": { + "$ref": "#/definitions/JSONCanvasColor", + "description": "The color of the node" + }, + "height": { + "type": "number", + "description": "The height of the node in pixels" + }, + "id": { + "type": "string", + "description": "Unique ID for the node" + }, + "type": { + "$ref": "#/definitions/NodeType", + "description": "The node type" + }, + "width": { + "type": "number", + "description": "The width of the node in pixels" + }, + "x": { + "type": "number", + "description": "The x position of the node in pixels" + }, + "y": { + "type": "number", + "description": "The y position of the node in pixels" + } + }, + "required": [ + "height", + "id", + "type", + "width", + "x", + "y" + ], + "additionalProperties": false + }, + "NodeType": { + "type": "string", + "enum": [ + "text", + "file", + "link", + "group" + ] + }, + "JSONCanvasTextNode": { + "type": "object", + "properties": { + "color": { + "$ref": "#/definitions/JSONCanvasColor", + "description": "The color of the node" + }, + "height": { + "type": "number", + "description": "The height of the node in pixels" + }, + "id": { + "type": "string", + "description": "Unique ID for the node" + }, + "type": { + "type": "string", + "const": "text", + "description": "The node type" + }, + "width": { + "type": "number", + "description": "The width of the node in pixels" + }, + "x": { + "type": "number", + "description": "The x position of the node in pixels" + }, + "y": { + "type": "number", + "description": "The y position of the node in pixels" + }, + "text": { + "type": "string", + "description": "Plain text with Markdown syntax" + } + }, + "required": [ + "height", + "id", + "text", + "type", + "width", + "x", + "y" + ], + "additionalProperties": false + }, + "JSONCanvasFileNode": { + "type": "object", + "properties": { + "color": { + "$ref": "#/definitions/JSONCanvasColor", + "description": "The color of the node" + }, + "height": { + "type": "number", + "description": "The height of the node in pixels" + }, + "id": { + "type": "string", + "description": "Unique ID for the node" + }, + "type": { + "type": "string", + "const": "file", + "description": "The node type" + }, + "width": { + "type": "number", + "description": "The width of the node in pixels" + }, + "x": { + "type": "number", + "description": "The x position of the node in pixels" + }, + "y": { + "type": "number", + "description": "The y position of the node in pixels" + }, + "file": { + "type": "string", + "description": "The path to the file within the system" + }, + "subpath": { + "type": "string", + "description": "The subpath that may link to a heading or a block. Always starts with a #" + } + }, + "required": [ + "file", + "height", + "id", + "type", + "width", + "x", + "y" + ], + "additionalProperties": false + }, + "JSONCanvasLinkNode": { + "type": "object", + "properties": { + "color": { + "$ref": "#/definitions/JSONCanvasColor", + "description": "The color of the node" + }, + "height": { + "type": "number", + "description": "The height of the node in pixels" + }, + "id": { + "type": "string", + "description": "Unique ID for the node" + }, + "type": { + "type": "string", + "const": "link", + "description": "The node type" + }, + "width": { + "type": "number", + "description": "The width of the node in pixels" + }, + "x": { + "type": "number", + "description": "The x position of the node in pixels" + }, + "y": { + "type": "number", + "description": "The y position of the node in pixels" + }, + "url": { + "type": "string", + "description": "The URL to link to" + } + }, + "required": [ + "height", + "id", + "type", + "url", + "width", + "x", + "y" + ], + "additionalProperties": false + }, + "JSONCanvasGroupNode": { + "type": "object", + "properties": { + "color": { + "$ref": "#/definitions/JSONCanvasColor", + "description": "The color of the node" + }, + "height": { + "type": "number", + "description": "The height of the node in pixels" + }, + "id": { + "type": "string", + "description": "Unique ID for the node" + }, + "type": { + "type": "string", + "const": "group", + "description": "The node type" + }, + "width": { + "type": "number", + "description": "The width of the node in pixels" + }, + "x": { + "type": "number", + "description": "The x position of the node in pixels" + }, + "y": { + "type": "number", + "description": "The y position of the node in pixels" + }, + "background": { + "type": "string", + "description": "The path to the background image" + }, + "backgroundStyle": { + "$ref": "#/definitions/TheRenderingStyleOfABackgroundImage", + "description": "The rendering style of the background image" + }, + "label": { + "type": "string", + "description": "The text label for the group" + } + }, + "required": [ + "height", + "id", + "type", + "width", + "x", + "y" + ], + "additionalProperties": false + }, + "TheRenderingStyleOfABackgroundImage": { + "type": "string", + "enum": [ + "cover", + "ratio", + "repeat" + ], + "description": "Options are: cover - fills the entire width and height of the node. ratio - maintains the aspect ratio of the background image. repeat - repeats the image as a pattern in both x/y directions." + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..98f752c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,361 @@ +{ + "name": "jsoncanvas", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jsoncanvas", + "license": "ISC", + "dependencies": { + "ts-json-schema-generator": "^1.5.0" + }, + "devDependencies": { + "@types/node": "^20.11.27", + "ts-node": "^10.9.2", + "typescript": "^5.4.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/node": { + "version": "20.11.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", + "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-json-schema-generator": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.0.tgz", + "integrity": "sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA==", + "dependencies": { + "@types/json-schema": "^7.0.12", + "commander": "^11.0.0", + "glob": "^8.0.3", + "json5": "^2.2.3", + "normalize-path": "^3.0.0", + "safe-stable-stringify": "^2.4.3", + "typescript": "~5.3.2" + }, + "bin": { + "ts-json-schema-generator": "bin/ts-json-schema-generator" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ts-json-schema-generator/node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4c38bb7 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "jsoncanvas", + "description": "Infinite canvas tools are a way to view and organize information spatially, like a digital whiteboard. Infinite canvases encourage freedom and exploration, and have become a popular interface pattern across many apps.", + "scripts": { + "generate": "node --loader ts-node/esm --inspect ./src/util/generate.ts ./src/util/generate.ts JsonCanvas" + }, + "license": "ISC", + "devDependencies": { + "@types/node": "^20.11.27", + "ts-node": "^10.9.2", + "typescript": "^5.4.2" + }, + "dependencies": { + "ts-json-schema-generator": "^1.5.0" + }, + "type": "module" +} diff --git a/sample.canvas b/sample.canvas index 87dafb1..30855a3 100644 --- a/sample.canvas +++ b/sample.canvas @@ -1,4 +1,5 @@ { + "$schema": "./jsoncanvas.schema.json", "nodes":[ {"id":"8132d4d894c80022","type":"file","file":"readme.md","x":-280,"y":-200,"width":570,"height":560,"color":"6"}, {"id":"7efdbbe0c4742315","type":"file","file":"_site/logo.svg","x":-280,"y":-440,"width":217,"height":80}, @@ -8,4 +9,4 @@ "edges":[ {"id":"6fa11ab87f90b8af","fromNode":"7efdbbe0c4742315","fromSide":"right","toNode":"59e896bc8da20699","toSide":"left"} ] -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4bd96e9 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,164 @@ +export type JSONCanvasColor = string | APresetColor; +/** + * Six preset colors exist, mapped to the following numbers: + * 1 red + * 2 orange + * 3 yellow + * 4 green + * 5 cyan + * 6 purple + */ +export const APresetColor = { + Red: "1", + Orange: "2", + Yellow: "3", + Green: "4", + Cyan: "5", + Purple: "6", +} as const; +export type APresetColor = (typeof APresetColor)[keyof typeof APresetColor]; + +/** + * The rendering style of the end of the edge line + */ +export type JSONCanvasEdgeEnd = "none" | "arrow"; + +export type JSONCanvasNode = + | JSONCanvasNodeGeneric + | JSONCanvasTextNode + | JSONCanvasFileNode + | JSONCanvasLinkNode + | JSONCanvasGroupNode; + +/** + * Options are: + * cover - fills the entire width and height of the node. + * ratio - maintains the aspect ratio of the background image. + * repeat - repeats the image as a pattern in both x/y directions. + */ +export type TheRenderingStyleOfABackgroundImage = "cover" | "ratio" | "repeat"; + +export interface JsonCanvas { + /** + * Edges are lines that connect one node to another + */ + edges?: JSONCanvasEdge[]; + /** + * Nodes are objects within the canvas. Nodes may be text, files, links, or groups + */ + nodes?: JSONCanvasNode[]; + [k: string]: unknown; +} +type Side = "top" | "right" | "bottom" | "left"; + +export interface JSONCanvasEdge { + color?: JSONCanvasColor; + /** + * The ID of the node that the edge starts from + */ + fromNode: string; + /** + * The side of the node that the edge connects from + */ + fromSide?: Side; + /** + * The ID for the edge + */ + id: string; + /** + * The text label for the edge + */ + label?: string; + /** + * The rendering style of the end of the edge line + */ + toEnd?: JSONCanvasEdgeEnd; + /** + * The ID of the node that the edge ends at + */ + toNode: string; + /** + * The side of the node that the edge connects to + */ + toSide?: Side; +} + +export const NodeType = { + Text: "text", + File: "file", + Link: "link", + Group: "group", +} as const; + +export type NodeType = (typeof NodeType)[keyof typeof NodeType]; + +export interface JSONCanvasNodeGeneric { + /** + * The color of the node + */ + color?: JSONCanvasColor; + /** + * The height of the node in pixels + */ + height: number; + /** + * Unique ID for the node + */ + id: string; + /** + * The node type + */ + type: NodeType; + /** + * The width of the node in pixels + */ + width: number; + /** + * The x position of the node in pixels + */ + x: number; + /** + * The y position of the node in pixels + */ + y: number; +} +export interface JSONCanvasTextNode extends JSONCanvasNodeGeneric { + type: typeof NodeType.Text; + /** + * Plain text with Markdown syntax + */ + text: string; +} +export interface JSONCanvasFileNode extends JSONCanvasNodeGeneric { + type: typeof NodeType.File; + /** + * The path to the file within the system + */ + file: string; + /** + * The subpath that may link to a heading or a block. Always starts with a # + */ + subpath?: string; +} +export interface JSONCanvasLinkNode extends JSONCanvasNodeGeneric { + type: typeof NodeType.Link; + /** + * The URL to link to + */ + url: string; +} +export interface JSONCanvasGroupNode extends JSONCanvasNodeGeneric { + type: typeof NodeType.Group; + /** + * The path to the background image + */ + background?: string; + /** + * The rendering style of the background image + */ + backgroundStyle?: TheRenderingStyleOfABackgroundImage; + /** + * The text label for the group + */ + label?: string; +} diff --git a/src/util/generate.ts b/src/util/generate.ts new file mode 100644 index 0000000..b009974 --- /dev/null +++ b/src/util/generate.ts @@ -0,0 +1,53 @@ +import fs from "fs"; +import path, { dirname } from "path"; +import { Config, createGenerator } from "ts-json-schema-generator"; +import { fileURLToPath } from "url"; + +const file = process.argv[2]; +if (!file) { + console.error("No file path provided"); + process.exit(1); +} else if (!fs.existsSync(file)) { + console.error("Provided path does not exist"); + process.exit(1); +} else if (!fs.lstatSync(file).isFile()) { + console.error("Provided path is not a file"); + process.exit(1); +} else if (!file.endsWith(".ts")) { + console.error("Provided file is not a typescript file"); + process.exit(1); +} +const rootType = process.argv[3].trim(); +if (!rootType) { + console.error("No root type provided"); + process.exit(1); +} else if (rootType.length === 0) { + console.error("Root type cannot be empty"); + process.exit(1); +} + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const projectRoot = path.resolve(__dirname, "../../"); +const srcRoot = path.join(projectRoot, "src"); +const tsconfig = path.join(projectRoot, "tsconfig.json"); + +function generate(file: string, rootType: string = "JsonCanvas") { + const relativePath = path.relative(__dirname, file); + const config: Config = { + path: relativePath, + tsconfig: tsconfig, + type: rootType, + topRef: true, + additionalProperties: false, + sortProps: true, + }; + + const outputPath = path.join(projectRoot, "jsoncanvas.schema.json"); + const gnerator = createGenerator(config); + const schema = gnerator.createSchema(config.type); + const schemaString = JSON.stringify(schema, null, 2); + fs.writeFileSync(outputPath, schemaString); +} + +generate(file); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0b7a0e2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "moduleResolution": "Node", + "module": "ESNext", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + } +}