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 cfea767..3983bbc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ _site .jekyll-metadata .obsidian vendor +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..3f382d1 --- /dev/null +++ b/jsoncanvas.schema.json @@ -0,0 +1,275 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/JsonCanvas", + "definitions": { + "JsonCanvas": { + "type": "object", + "additionalProperties": {}, + "properties": { + "$schema": { + "type": "string" + }, + "nodes": { + "$ref": "#/definitions/AnOptionalArrayOfNodes" + }, + "edges": { + "$ref": "#/definitions/AnOptionalArrayOfEdges" + } + }, + "required": [ + "$schema" + ] + }, + "AnOptionalArrayOfNodes": { + "type": "array", + "items": { + "$ref": "#/definitions/ANode" + } + }, + "ANode": { + "anyOf": [ + { + "$ref": "#/definitions/TextTypeNodesStoreText" + }, + { + "$ref": "#/definitions/FileTypeNodesReferenceOtherFilesOrAttachmentsSuchAsImagesVideosEtc" + }, + { + "$ref": "#/definitions/LinkTypeNodesReferenceAURL" + }, + { + "$ref": "#/definitions/GroupTypeNodesAreUsedAsAVisualContainerForNodesWithinIt" + } + ], + "description": "Nodes are objects within the canvas. Nodes may be text, files, links, or groups." + }, + "TextTypeNodesStoreText": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/TheTypeOfATextNode" + }, + "text": { + "$ref": "#/definitions/TextInPlainTextWithMarkdownSyntax" + } + }, + "required": [ + "type", + "text" + ], + "additionalProperties": {} + }, + "TheTypeOfATextNode": { + "type": "string", + "const": "text" + }, + "TextInPlainTextWithMarkdownSyntax": { + "type": "string" + }, + "FileTypeNodesReferenceOtherFilesOrAttachmentsSuchAsImagesVideosEtc": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/TheTypeOfAFileNode" + }, + "file": { + "$ref": "#/definitions/ThePathToTheFileWithinTheSystem" + }, + "subpath": { + "$ref": "#/definitions/ASubpathThatMayLinkToAHeadingOrABlockAlwaysStartsWith" + } + }, + "required": [ + "type", + "file" + ], + "additionalProperties": {} + }, + "TheTypeOfAFileNode": { + "type": "string", + "const": "file" + }, + "ThePathToTheFileWithinTheSystem": { + "type": "string" + }, + "ASubpathThatMayLinkToAHeadingOrABlockAlwaysStartsWith": { + "type": "string" + }, + "LinkTypeNodesReferenceAURL": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/TheTypeOfALinkNode" + }, + "url": { + "$ref": "#/definitions/TheURLReferencedByTheLink" + } + }, + "required": [ + "type", + "url" + ], + "additionalProperties": {} + }, + "TheTypeOfALinkNode": { + "type": "string", + "const": "link" + }, + "TheURLReferencedByTheLink": { + "type": "string" + }, + "GroupTypeNodesAreUsedAsAVisualContainerForNodesWithinIt": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/TheTypeOfAGroupNode" + }, + "label": { + "$ref": "#/definitions/ATextLabelForTheGroup" + }, + "background": { + "$ref": "#/definitions/ThePathToTheBackgroundImage" + }, + "backgroundStyle": { + "$ref": "#/definitions/TheRenderingStyleOfTheBackgroundImage" + } + }, + "required": [ + "type" + ], + "additionalProperties": {} + }, + "TheTypeOfAGroupNode": { + "type": "string", + "const": "text" + }, + "ATextLabelForTheGroup": { + "type": "string" + }, + "ThePathToTheBackgroundImage": { + "type": "string" + }, + "TheRenderingStyleOfTheBackgroundImage": { + "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." + }, + "AnOptionalArrayOfEdges": { + "type": "array", + "items": { + "$ref": "#/definitions/EdgesAreLinesThatConnectOneNodeToAnother" + } + }, + "EdgesAreLinesThatConnectOneNodeToAnother": { + "type": "object", + "properties": { + "id": { + "$ref": "#/definitions/AUniqueIdentifierForTheEdge" + }, + "fromNode": { + "$ref": "#/definitions/TheNodeIdWhereTheConnectionStarts" + }, + "fromSide": { + "$ref": "#/definitions/TheSideWhereThisEdgeStarts" + }, + "fromEnd": { + "$ref": "#/definitions/TheShapeOfTheEndpointAtTheEdgeStart" + }, + "toNode": { + "$ref": "#/definitions/TheNodeIdWhereTheConnectionEnds" + }, + "toSide": { + "$ref": "#/definitions/TheSideWhereThisEdgeEnds" + }, + "toEnd": { + "$ref": "#/definitions/TheShapeOfTheEndpointAtTheEdgeEnd" + }, + "color": { + "$ref": "#/definitions/TheColorOfTheLine" + }, + "label": { + "$ref": "#/definitions/TheTextLabelForTheEdge" + } + }, + "required": [ + "id", + "fromNode", + "toNode" + ], + "additionalProperties": {} + }, + "AUniqueIdentifierForTheEdge": { + "type": "string" + }, + "TheNodeIdWhereTheConnectionStarts": { + "type": "string" + }, + "TheSideWhereThisEdgeStarts": { + "type": "string", + "enum": [ + "top", + "right", + "bottom", + "left" + ] + }, + "TheShapeOfTheEndpointAtTheEdgeStart": { + "type": "string", + "enum": [ + "none", + "arrow" + ] + }, + "TheNodeIdWhereTheConnectionEnds": { + "type": "string" + }, + "TheSideWhereThisEdgeEnds": { + "type": "string", + "enum": [ + "top", + "right", + "bottom", + "left" + ] + }, + "TheShapeOfTheEndpointAtTheEdgeEnd": { + "type": "string", + "enum": [ + "none", + "arrow" + ] + }, + "TheColorOfTheLine": { + "anyOf": [ + { + "$ref": "#/definitions/AColorInHexadecimalFormat" + }, + { + "$ref": "#/definitions/APresetColor" + } + ] + }, + "AColorInHexadecimalFormat": { + "type": "string" + }, + "APresetColor": { + "type": "number", + "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" + }, + "TheTextLabelForTheEdge": { + "type": "string" + } + } +} \ No newline at end of file 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 24a5902..e6ea54f 100644 --- a/sample.canvas +++ b/sample.canvas @@ -1,11 +1,51 @@ { - "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}, - {"id":"59e896bc8da20699","type":"text","text":"Learn more:\n\n- [Readme](readme.md)\n- [Spec](version/1.0.md)\n- [Github](https://github.com/obsidianmd/jsoncanvas)","x":40,"y":-440,"width":250,"height":160}, - {"id":"0ba565e7f30e0652","type":"file","file":"spec/1.0.md","x":360,"y":-400,"width":400,"height":400} - ], - "edges":[ - {"id":"6fa11ab87f90b8af","fromNode":"7efdbbe0c4742315","fromSide":"right","toNode":"59e896bc8da20699","toSide":"left"} - ] -} \ No newline at end of file + "$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 + }, + { + "id": "59e896bc8da20699", + "type": "text", + "text": "Learn more:\n\n- [Readme](readme.md)\n- [Spec](version/1.0.md)\n- [Github](https://github.com/obsidianmd/jsoncanvas)", + "x": 40, + "y": -440, + "width": 250, + "height": 160 + }, + { + "id": "0ba565e7f30e0652", + "type": "file", + "file": "spec/1.0.md", + "x": 360, + "y": -400, + "width": 400, + "height": 400 + } + ], + "edges": [ + { + "id": "6fa11ab87f90b8af", + "fromNode": "7efdbbe0c4742315", + "fromSide": "right", + "toNode": "59e896bc8da20699", + "toSide": "left" + } + ] +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d9eec3a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,100 @@ +/** + * Nodes are objects within the canvas. Nodes may be text, files, links, or groups. + */ +export type ANode = + | TextTypeNodesStoreText + | FileTypeNodesReferenceOtherFilesOrAttachmentsSuchAsImagesVideosEtc + | LinkTypeNodesReferenceAURL + | GroupTypeNodesAreUsedAsAVisualContainerForNodesWithinIt; +export type TheTypeOfATextNode = "text"; +export type TextInPlainTextWithMarkdownSyntax = string; +export type TheTypeOfAFileNode = "file"; +export type ThePathToTheFileWithinTheSystem = string; +export type ASubpathThatMayLinkToAHeadingOrABlockAlwaysStartsWith = string; +export type TheTypeOfALinkNode = "link"; +export type TheURLReferencedByTheLink = string; +export type TheTypeOfAGroupNode = "text"; +export type ATextLabelForTheGroup = string; +export type ThePathToTheBackgroundImage = string; +/** + * 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 TheRenderingStyleOfTheBackgroundImage = + | "cover" + | "ratio" + | "repeat"; +export type AnOptionalArrayOfNodes = ANode[]; +export type AUniqueIdentifierForTheEdge = string; +export type TheNodeIdWhereTheConnectionStarts = string; +export type TheSideWhereThisEdgeStarts = "top" | "right" | "bottom" | "left"; +export type TheShapeOfTheEndpointAtTheEdgeStart = "none" | "arrow"; +export type TheNodeIdWhereTheConnectionEnds = string; +export type TheSideWhereThisEdgeEnds = "top" | "right" | "bottom" | "left"; +export type TheShapeOfTheEndpointAtTheEdgeEnd = "none" | "arrow"; +export type TheColorOfTheLine = AColorInHexadecimalFormat | APresetColor; +export type AColorInHexadecimalFormat = string; +/** + * Six preset colors exist, mapped to the following numbers: + * 1 red + * 2 orange + * 3 yellow + * 4 green + * 5 cyan + * 6 purple + */ +export type APresetColor = 1 | 2 | 3 | 4 | 5 | 6; +export type TheTextLabelForTheEdge = string; +export type AnOptionalArrayOfEdges = EdgesAreLinesThatConnectOneNodeToAnother[]; + +/** + * 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. + * The JSON Canvas format was created to provide longevity, readability, interoperability, and extensibility to data created with infinite canvas apps. The format is designed to be easy to parse and give users ownership over their data. JSON Canvas files use the .canvas extension. + * JSON Canvas was originally created for Obsidian. JSON Canvas can be implemented freely as an import, export, and storage format for any app or tool. This site, and all the resources associated with JSON Canvas are open source under the MIT license. + */ +export interface AnOpenFileFormatForInfiniteCanvasData { + $schema: string; + nodes?: AnOptionalArrayOfNodes; + edges?: AnOptionalArrayOfEdges; + [k: string]: unknown; +} +export default interface JsonCanvas + extends AnOpenFileFormatForInfiniteCanvasData {} + +export interface TextTypeNodesStoreText { + type: TheTypeOfATextNode; + text: TextInPlainTextWithMarkdownSyntax; + [k: string]: unknown; +} +export interface FileTypeNodesReferenceOtherFilesOrAttachmentsSuchAsImagesVideosEtc { + type: TheTypeOfAFileNode; + file: ThePathToTheFileWithinTheSystem; + subpath?: ASubpathThatMayLinkToAHeadingOrABlockAlwaysStartsWith; + [k: string]: unknown; +} +export interface LinkTypeNodesReferenceAURL { + type: TheTypeOfALinkNode; + url: TheURLReferencedByTheLink; + [k: string]: unknown; +} +export interface GroupTypeNodesAreUsedAsAVisualContainerForNodesWithinIt { + type: TheTypeOfAGroupNode; + label?: ATextLabelForTheGroup; + background?: ThePathToTheBackgroundImage; + backgroundStyle?: TheRenderingStyleOfTheBackgroundImage; + [k: string]: unknown; +} +export interface EdgesAreLinesThatConnectOneNodeToAnother { + id: AUniqueIdentifierForTheEdge; + fromNode: TheNodeIdWhereTheConnectionStarts; + fromSide?: TheSideWhereThisEdgeStarts; + fromEnd?: TheShapeOfTheEndpointAtTheEdgeStart; + toNode: TheNodeIdWhereTheConnectionEnds; + toSide?: TheSideWhereThisEdgeEnds; + toEnd?: TheShapeOfTheEndpointAtTheEdgeEnd; + color?: TheColorOfTheLine; + label?: TheTextLabelForTheEdge; + [k: string]: unknown; +} 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, + } +}