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..ba68bb7 --- /dev/null +++ b/jsoncanvas.schema.json @@ -0,0 +1,163 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/JsonCanvas", + "definitions": { + "JsonCanvas": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "$ref": "#/definitions/CanvasNode" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/definitions/Edge" + } + } + }, + "additionalProperties": false, + "description": "Top-level interface for the JSON Canvas Spec" + }, + "CanvasNode": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "text", + "file", + "link", + "group" + ] + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "color": { + "$ref": "#/definitions/CanvasColor" + } + }, + "required": [ + "id", + "type", + "x", + "y", + "width", + "height" + ], + "additionalProperties": false, + "description": "Base node interface for common attributes" + }, + "CanvasColor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number", + "const": 1 + }, + { + "type": "number", + "const": 2 + }, + { + "type": "number", + "const": 3 + }, + { + "type": "number", + "const": 4 + }, + { + "type": "number", + "const": 5 + }, + { + "type": "number", + "const": 6 + } + ], + "description": "Type for color, either a string for hex values or a number for preset colors" + }, + "Edge": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "fromNode": { + "type": "string" + }, + "fromSide": { + "type": "string", + "enum": [ + "top", + "right", + "bottom", + "left" + ], + "description": "Optional side of the node where the edge starts" + }, + "fromEnd": { + "type": "string", + "enum": [ + "none", + "arrow" + ], + "description": "Optional style of the edge end" + }, + "toNode": { + "type": "string" + }, + "toSide": { + "type": "string", + "enum": [ + "top", + "right", + "bottom", + "left" + ], + "description": "Optional side of the node where the edge ends" + }, + "toEnd": { + "type": "string", + "enum": [ + "none", + "arrow" + ], + "description": "Optional style of the edge end" + }, + "color": { + "$ref": "#/definitions/CanvasColor" + }, + "label": { + "type": "string", + "description": "Optional label for the edge" + } + }, + "required": [ + "id", + "fromNode", + "toNode" + ], + "additionalProperties": false, + "description": "Edge interface for connections between nodes" + } + } +} \ 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..4690034 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,118 @@ +/** + * Top-level interface for the JSON Canvas Spec + */ +export interface JsonCanvas { + nodes?: CanvasNode[]; + edges?: Edge[]; +} + +export default JsonCanvas; + +/** + * Base node interface for common attributes + */ +export interface CanvasNode { + id: string; + type: "text" | "file" | "link" | "group"; + x: number; + y: number; + width: number; + height: number; + color?: CanvasColor; +} + +/** + * Extended node interface for TextNode type + */ +export interface TextNode extends CanvasNode { + type: "text"; + /** + * Text content with Markdown syntax + */ + text: string; +} + +/** + * Extended node interface for FileNode type + */ +export interface FileNode extends CanvasNode { + type: "file"; + /** + * Path to the file + */ + file: string; + /** + * Optional subpath within the file + */ + subpath?: string; +} + +/** + * Extended node interface for LinkNode type + */ +export interface LinkNode extends CanvasNode { + type: "link"; + /** + * URL the node references + */ + url: string; +} + +/** + * Extended node interface for GroupNode type + */ +export interface GroupNode extends CanvasNode { + type: "group"; + /** + * Optional text label for the group + */ + label?: string; + /** + * Optional path to a background image + */ + background?: string; + /** + * Optional rendering style of the background + */ + backgroundStyle?: "cover" | "ratio" | "repeat"; +} + +/** + * Union type for all node types + */ +export type AllNodes = TextNode | FileNode | LinkNode | GroupNode; + +/** + * Edge interface for connections between nodes + */ +export interface Edge { + id: string; + fromNode: string; + /** + * Optional side of the node where the edge starts + */ + fromSide?: "top" | "right" | "bottom" | "left"; + /** + * Optional style of the edge end + */ + fromEnd?: "none" | "arrow"; + toNode: string; + /** + * Optional side of the node where the edge ends + */ + toSide?: "top" | "right" | "bottom" | "left"; + /** + * Optional style of the edge end + */ + toEnd?: "none" | "arrow"; + color?: CanvasColor; + /** + * Optional label for the edge + */ + label?: string; +} + +/** + * Type for color, either a string for hex values or a number for preset colors + */ +export type CanvasColor = string | 1 | 2 | 3 | 4 | 5 | 6; 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, + } +}