├── .github └── workflows │ ├── add-to-backlog.yml │ └── ci.yaml ├── .gitignore ├── LICENSE ├── README.md ├── env.d.ts ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public ├── apple-touch-icon.png ├── favicon-96x96.png ├── favicon.ico ├── favicon.svg ├── img │ └── icons │ │ └── zazuko_icon.svg ├── robots.txt ├── site.webmanifest ├── web-app-manifest-192x192.png └── web-app-manifest-512x512.png ├── src-vscode ├── .eslintrc.json ├── .vscode-test.mjs ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── images │ ├── preview-button.png │ └── zazuko.png ├── package-lock.json ├── package.json ├── src │ ├── constant │ │ ├── rdf-formats.ts │ │ └── update-event-type.ts │ ├── extension.ts │ ├── model │ │ └── update-message.ts │ └── rdf-preview-panel.ts ├── tsconfig.json └── webpack.config.js ├── src ├── App.vue ├── assets │ ├── logo.svg │ └── main.css ├── components │ ├── GraphView.vue │ ├── RdfEditor.vue │ ├── RdfTerm.vue │ ├── SPOSearch.vue │ ├── configuration │ │ └── AppConfiguration.vue │ ├── graph │ │ ├── floating-edge │ │ │ ├── FloatingEdge.vue │ │ │ └── floating-edge-utils.ts │ │ └── resource-node │ │ │ └── ResourceNode.vue │ ├── icons │ │ ├── IconCommunity.vue │ │ ├── IconDocumentation.vue │ │ ├── IconEcosystem.vue │ │ ├── IconSupport.vue │ │ └── IconTooling.vue │ └── share-button │ │ └── ShareButton.vue ├── constant │ ├── local-storage-keys.ts │ ├── min-max.ts │ └── rdf-format.ts ├── layout │ └── use-layout.ts ├── main.ts ├── model │ ├── link.model.ts │ ├── resource.model.ts │ └── tab.model.ts ├── rdf │ ├── environment.ts │ ├── prefix-map.ts │ └── shrink-term.ts ├── resources-utils.ts └── vscode-webview │ ├── AppVscode.vue │ ├── constant │ └── update-event-type.ts │ ├── main.ts │ └── model │ └── update-message.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.main.ts ├── vite.config.vscode.ts └── vscode └── index.html /.github/workflows/add-to-backlog.yml: -------------------------------------------------------------------------------- 1 | name: Add issues to shared backlog 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | pull_request: 8 | types: 9 | - opened 10 | 11 | jobs: 12 | add-to-project: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/add-to-project@v0.4.0 16 | with: 17 | project-url: https://github.com/orgs/zazuko/projects/23 18 | github-token: ${{ secrets.BACKLOG_PAT }} 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Build and deploy the app on GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | 12 | defaults: 13 | run: 14 | shell: bash 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: Set up NodeJS 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 20 24 | 25 | - name: Install dependencies 26 | run: npm ci 27 | 28 | - name: Run linter 29 | run: npm run lint 30 | 31 | - name: Build the app 32 | run: npm run build 33 | 34 | - name: Configure CNAME 35 | run: echo sketch.zazuko.com > dist/CNAME 36 | 37 | - name: Deploy to GitHub Pages 38 | uses: JamesIves/github-pages-deploy-action@v4 39 | with: 40 | branch: gh-pages 41 | folder: dist 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | 32 | .vscode 33 | 34 | src-vscode/media/assets/main.js 35 | src-vscode/media/ 36 | src-vscode/*.vsix 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Zazuko GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zazuko RDF Sketch 2 | 3 | This is a simple tool to Visualize RDF and provides the ability to Search and Navigate through the graph. 4 | This repository contains two parts: 5 | 1. The Web Application [https://sketch.zazuko.com](https://sketch.zazuko.com) 6 | 2. The VSCode Extension [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=Zazuko.vscode-rdf-sketch). 7 | 8 | 9 | ## Web Application 10 | 11 | The web application is a vue.js application and runs completely in the browser. 12 | 13 | ### Project setup 14 | 15 | ```sh 16 | npm install 17 | ``` 18 | 19 | ### Compiles and hot-reloads for development 20 | 21 | ```sh 22 | npm run dev 23 | ``` 24 | 25 | ### Compiles and minifies for production 26 | 27 | ```sh 28 | npm run build 29 | ``` 30 | 31 | ### The project is based on the following technologies: 32 | 33 | * [Vue.js](https://vuejs.org) 34 | * [Vue Flow](https://vueflow.dev) 35 | * [rdfjs-elements](https://github.com/zazuko/rdfjs-elements) 36 | 37 | 38 | 39 | ## VSCode Extension 40 | 41 | The VSCode Extension consists of two parts: 42 | 1. The Vue.js application (Webview) 43 | 2. The vscode extension part (Extension) 44 | 45 | ### Vue.js Webview 46 | The Vue.js application is located in the same root folder as the web application. 47 | The 'vite.config.vscode.js' is used to build the Vue.js application for the VSCode Extension. 48 | 49 | You can run the development server for the Vue.js application with the following command: 50 | 51 | ```sh 52 | npm run dev:vscode 53 | ``` 54 | 55 | You can access the Vue.js application in the browser at [http://localhost:5173/vscode/](http://localhost:5173/vscode/). 56 | 57 | It has its own index.html file 'vscode/index.html' which is used to load the Vue.js application and it fires an event to provide a sample Turtle string to test the application (more about that below). 58 | 59 | The folder 'vscode-webview' contains the Vue.js application. All other components are the same as in the web application. 60 | 61 | You can develop the webview application separately from the vscode extension. The 'vscode/index.html' is only used for development and is not part of the extension. The extension creates its own HTML code to load the Vue.js application into the webview. 62 | 63 | ### Build the Vue.js application (WebView) for the VSCode Extension 64 | 65 | ```sh 66 | npm run build:vscode 67 | ``` 68 | 69 | The target folder for the build is 'src-vscode/media'. The extension will use this folder to load the Vue.js application into the webview. 70 | 71 | ### VSCode Extension (Extension) 72 | The extension is located in the 'src-vscode' folder. 73 | 74 | ```sh 75 | cd src-vscode 76 | npm install 77 | ``` 78 | 79 | Open your VSCode IDE in the Folder 'src-vscode' and run the extension with F5. 80 | 81 | It will compile the extension with the Vue.js application contained in the 'media' folder and open a new VSCode window with the extension running. 82 | 83 | #### Communication between the Extension and the WebView 84 | 85 | All paths in the documentation here are relative to the 'src-vscode' folder. 86 | 87 | The communication between the extension and the webview is done with the vscode API and the webview API. The extension fires an event to the webview and the webview listens to this event. This is done in the RdfPreviewPanel class located in 'src/rdf-preview-panel.ts'. 88 | 89 | The RdfPreviewPanel class generates the HTML to load the web view and installs an Event handler to listen to the vscode specific event and emits an event again as a 'vscode free event'. It's done like this to develop the WebView separately from the extension. 90 | 91 | #### Dark Mode 92 | 93 | This is not important for most readers. Just an explanation to explain wired things in the code. 94 | 95 | The WebView in Vue.js (Theming of primevue) needs a dark mode class on the top level HTML element. VSCode is adding the class 'vscode-dark' to the BODY element. 96 | 97 | There is a part in the WebView code observing the class of the BODY element and adding the class 'vscode-dark' to the top level HTML element. 98 | 99 | 100 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import pluginVue from 'eslint-plugin-vue'; 2 | import vueTsEslintConfig from '@vue/eslint-config-typescript'; 3 | 4 | export default [ 5 | { 6 | name: 'app/files-to-lint', 7 | files: ['**/*.{ts,mts,tsx,vue}'], 8 | }, 9 | { 10 | name: 'app/files-to-ignore', 11 | ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**', '**/src-vscode/**'], 12 | }, 13 | ...pluginVue.configs['flat/essential'], 14 | ...vueTsEslintConfig(), 15 | { 16 | rules: { 17 | "@typescript-eslint/no-explicit-any": 'warn', 18 | "@typescript-eslint/no-empty-object-type": 'warn', 19 | } 20 | }, 21 | ]; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Zazuko RDF Sketch 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rdf-sketch", 3 | "version": "1.0.6", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --config vite.config.main.ts", 8 | "build": "vite build --config vite.config.main.ts", 9 | "preview": "vite preview", 10 | "build-only": "vite build", 11 | "type-check": "vue-tsc --build --force", 12 | "lint": "eslint .", 13 | "build:vscode": "vite build --config vite.config.vscode.ts", 14 | "dev:vscode": "vite --config vite.config.vscode.ts" 15 | }, 16 | "dependencies": { 17 | "@esbuild-plugins/node-modules-polyfill": "^0.2.2", 18 | "@primeuix/themes": "^1.0.0", 19 | "@rdfjs-elements/rdf-editor": "^0.5.10", 20 | "@vue-flow/core": "^1.42.4", 21 | "@zazuko/env": "^2.5.1", 22 | "@zazuko/prefixes": "^2.3.0", 23 | "elkjs": "^0.10.0", 24 | "primeicons": "^7.0.0", 25 | "primevue": "^4.3.2", 26 | "util": "^0.12.5", 27 | "vue": "^3.5.13" 28 | }, 29 | "devDependencies": { 30 | "@esbuild-plugins/node-globals-polyfill": "^0.2.3", 31 | "@rdfjs/types": "^1.1.2", 32 | "@rollup/plugin-inject": "^5.0.5", 33 | "@rushstack/eslint-patch": "^1.10.5", 34 | "@tsconfig/node20": "^20.1.4", 35 | "@types/node": "^20.14.5", 36 | "@vitejs/plugin-vue": "^5.2.1", 37 | "@vue/eslint-config-typescript": "^14.4.0", 38 | "@vue/tsconfig": "^0.7.0", 39 | "eslint": "^9.20.0", 40 | "eslint-plugin-vue": "^9.32.0", 41 | "npm-run-all2": "^6.2.0", 42 | "rollup-plugin-node-polyfills": "^0.2.1", 43 | "typescript": "~5.7.3", 44 | "vite": "^5.4.10", 45 | "vite-plugin-pwa": "^0.21.0", 46 | "vue-tsc": "^2.2.0" 47 | } 48 | } -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazuko/rdf-sketch/4fe6248673dec584faf352c4ce2ba42d5b7ba258/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazuko/rdf-sketch/4fe6248673dec584faf352c4ce2ba42d5b7ba258/public/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazuko/rdf-sketch/4fe6248673dec584faf352c4ce2ba42d5b7ba258/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/img/icons/zazuko_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Zazuko RDF Sketch", 3 | "short_name": "Sketch", 4 | "icons": [ 5 | { 6 | "src": "/web-app-manifest-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "maskable" 10 | }, 11 | { 12 | "src": "/web-app-manifest-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "maskable" 16 | } 17 | ], 18 | "theme_color": "#ffffff", 19 | "background_color": "#ffffff", 20 | "display": "standalone" 21 | } -------------------------------------------------------------------------------- /public/web-app-manifest-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazuko/rdf-sketch/4fe6248673dec584faf352c4ce2ba42d5b7ba258/public/web-app-manifest-192x192.png -------------------------------------------------------------------------------- /public/web-app-manifest-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazuko/rdf-sketch/4fe6248673dec584faf352c4ce2ba42d5b7ba258/public/web-app-manifest-512x512.png -------------------------------------------------------------------------------- /src-vscode/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": [ 13 | "warn", 14 | { 15 | "selector": "import", 16 | "format": [ "camelCase", "PascalCase" ] 17 | } 18 | ], 19 | "@typescript-eslint/semi": "warn", 20 | "curly": "warn", 21 | "eqeqeq": "warn", 22 | "no-throw-literal": "warn", 23 | "semi": "off" 24 | }, 25 | "ignorePatterns": [ 26 | "out", 27 | "dist", 28 | "**/*.d.ts" 29 | ] 30 | } -------------------------------------------------------------------------------- /src-vscode/.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@vscode/test-cli'; 2 | 3 | export default defineConfig({ 4 | files: 'out/test/**/*.test.js', 5 | }); 6 | -------------------------------------------------------------------------------- /src-vscode/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher", "ms-vscode.extension-test-runner"] 5 | } 6 | -------------------------------------------------------------------------------- /src-vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src-vscode/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 13 | } -------------------------------------------------------------------------------- /src-vscode/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$ts-webpack-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never", 13 | "group": "watchers" 14 | }, 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | } 19 | }, 20 | { 21 | "type": "npm", 22 | "script": "watch-tests", 23 | "problemMatcher": "$tsc-watch", 24 | "isBackground": true, 25 | "presentation": { 26 | "reveal": "never", 27 | "group": "watchers" 28 | }, 29 | "group": "build" 30 | }, 31 | { 32 | "label": "tasks: watch-tests", 33 | "dependsOn": [ 34 | "npm: watch", 35 | "npm: watch-tests" 36 | ], 37 | "problemMatcher": [] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src-vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | webpack.config.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/.eslintrc.json 12 | **/*.map 13 | **/*.ts 14 | **/.vscode-test.* 15 | -------------------------------------------------------------------------------- /src-vscode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.0.6 4 | - Update dependencies 5 | 6 | ## 1.0.5 7 | - Improve SPO scroll performance 8 | - Unique rdf:type Icon 9 | 10 | ## 1.0.4 11 | - Add rdf:type Icon 12 | 13 | ## 1.0.3 14 | - Fix missing icon in readme.md 15 | 16 | ## 1.0.2 17 | - Activate the extension by file extension and not just langageId 18 | 19 | ## 1.0.1 20 | - Simplified jsonld support 21 | 22 | ## 1.0.0 23 | - New release with a lot of changes -------------------------------------------------------------------------------- /src-vscode/LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | The MIT License (MIT) 3 | Copyright © 2024 Zazuko GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src-vscode/README.md: -------------------------------------------------------------------------------- 1 | # VSCode RDF Sketch 2 | 3 | This extension provides a way to visualize RDF data in [Visual Studio Code](https://code.visualstudio.com). 4 | 5 | This extension is based on our [Zazuko Sketch](https://sketch.zazuko.com/) web app. Code is available [here](https://github.com/zazuko/rdf-sketch). 6 | 7 | ## Features 8 | 9 | * Visualize RDF data in a separate Visual Studio Code tab 10 | * Auto-layout & zoom 11 | * Search for nodes 12 | * Move nodes around 13 | * Navigate to the source node by clicking on the edge 14 | * Navigate to the destination node by clicking Object in the triple 15 | 16 | ## Installation 17 | 18 | You can install it directly from the Visual Studio Code Extension tab. It is available on the [Marketplace](https://marketplace.visualstudio.com/items?itemName=Zazuko.vscode-rdf-sketch) 19 | 20 | ## Usage 21 | 22 | 1. Open an RDF file in Visual Studio Code 23 | 2. Then click the preview icon on the top right corner of the editor ![preview-button](https://github.com/user-attachments/assets/a9ea1a28-3fd1-43bd-a1f7-065fb2e7ef3a) 24 | 25 | 4. A new tab will open with the visualization 26 | 27 | # Demo 28 | The preview is updated on typing. 29 | ![live-update](https://github.com/user-attachments/assets/6c59aa96-e60d-4c16-acdf-a286a3d63e12) 30 | 31 | You can click on objects of a triple to navigate to the corresponding node. And you can click on edges to move to the source node. 32 | ![navigate](https://github.com/user-attachments/assets/66515004-bb57-4608-b6a7-8306ec0eca8f) 33 | 34 | You can search by Subject, Predicate, or Object and click to move to the Node. In order to find nodes. 35 | ![search](https://github.com/user-attachments/assets/73e3ba40-1b2b-4310-b1ec-dc7a1eb94497) 36 | 37 | ## Supported RDF formats 38 | 39 | * Turtle 40 | * NTriples 41 | * NQuads 42 | * JSONLD 43 | * Trig 44 | 45 | ### Limitations 46 | 47 | * While you can move around boxes, the layout will not persist. Every time something in the data changes, it will auto-layout again and discard what you did before. 48 | * [YMMV](https://www.urbandictionary.com/define.php?term=ymmv) regarding how much data and what kind of graph you can visualize in a useful way. 49 | -------------------------------------------------------------------------------- /src-vscode/images/preview-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazuko/rdf-sketch/4fe6248673dec584faf352c4ce2ba42d5b7ba258/src-vscode/images/preview-button.png -------------------------------------------------------------------------------- /src-vscode/images/zazuko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazuko/rdf-sketch/4fe6248673dec584faf352c4ce2ba42d5b7ba258/src-vscode/images/zazuko.png -------------------------------------------------------------------------------- /src-vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-rdf-sketch", 3 | "displayName": "RDF Sketch", 4 | "description": "Graphical rendering of RDF data", 5 | "version": "1.0.5", 6 | "license": "MIT", 7 | "icon": "images/zazuko.png", 8 | "publisher": "Zazuko", 9 | "engines": { 10 | "vscode": "^1.95.0" 11 | }, 12 | "categories": [ 13 | "Visualization", 14 | "Data Science", 15 | "Education" 16 | ], 17 | "keywords": [ 18 | "RDF", 19 | "Turtle", 20 | "Preview" 21 | ], 22 | "repository": { 23 | "url": "https://github.com/zazuko/rdf-sketch" 24 | }, 25 | "activationEvents": [], 26 | "main": "./dist/extension.js", 27 | "contributes": { 28 | "commands": [ 29 | { 30 | "command": "rdf-sketch.openPreview", 31 | "title": "RDF: Open preview", 32 | "icon": "$(open-preview)" 33 | } 34 | ], 35 | "menus": { 36 | "editor/title": [ 37 | { 38 | "when": "resourceLangId == turtle || resourceExtname == .ttl || resourceLangId === n-quads || resourceExtname == .nq || resourceLangId == ntriples || resourceExtname == .nt || resourceLangId == rdf-xml || resourceExtname == .rdf || resourceLangId == trig || resourceExtname == .trig || resourceExtname == .jsonld", 39 | "command": "rdf-sketch.openPreview", 40 | "group": "navigation", 41 | "icon": "$(open-preview)", 42 | "arguments": [ 43 | { 44 | "uri": "${resource}" 45 | } 46 | ] 47 | } 48 | ], 49 | "commandPalette": [ 50 | { 51 | "command": "rdf-sketch.openPreview", 52 | "when": "false" 53 | } 54 | ] 55 | } 56 | }, 57 | "scripts": { 58 | "vscode:prepublish": "npm run build", 59 | "compile": "webpack", 60 | "watch": "webpack --watch", 61 | "build": "cd .. && npm run build:vscode && cd src-vscode && webpack --mode production --devtool hidden-source-map", 62 | "compile-tests": "tsc -p . --outDir out", 63 | "watch-tests": "tsc -p . -w --outDir out", 64 | "pretest": "npm run compile-tests && npm run compile && npm run lint", 65 | "lint": "eslint src --ext ts", 66 | "test": "vscode-test", 67 | "package": "npx vsce package", 68 | "publish": "npx vsce publish" 69 | }, 70 | "devDependencies": { 71 | "@types/vscode": "^1.95.0", 72 | "@types/mocha": "^10.0.6", 73 | "@types/node": "18.x", 74 | "@typescript-eslint/eslint-plugin": "^6.15.0", 75 | "@typescript-eslint/parser": "^6.15.0", 76 | "eslint": "^8.56.0", 77 | "typescript": "^5.3.3", 78 | "ts-loader": "^9.5.1", 79 | "webpack": "^5.89.0", 80 | "webpack-cli": "^5.1.4", 81 | "@vscode/test-cli": "^0.0.4", 82 | "@vscode/test-electron": "^2.3.8" 83 | } 84 | } -------------------------------------------------------------------------------- /src-vscode/src/constant/rdf-formats.ts: -------------------------------------------------------------------------------- 1 | export enum RdfSerializationType { 2 | JsonLD = 'application/ld+json', 3 | Trig = 'application/trig', 4 | NQuads = 'application/n-quads', 5 | NTriples = 'application/n-triples', 6 | // N3 = 'text/n3', 7 | Turtle = 'text/turtle', 8 | RdfXML = 'application/rdf+xml', 9 | } 10 | 11 | export const rdfFormats: RdfFormat[] = [ 12 | { 13 | contentType: RdfSerializationType.Turtle, 14 | name: 'Turtle', 15 | type: RdfSerializationType.Turtle, 16 | vscodeLanguageId: 'turtle', 17 | vscodeFileExtension: 'ttl', 18 | }, 19 | { 20 | contentType: RdfSerializationType.JsonLD, 21 | name: 'JSON-LD', 22 | type: RdfSerializationType.JsonLD, 23 | vscodeLanguageId: 'jsonld', 24 | vscodeFileExtension: 'jsonld', 25 | }, 26 | { 27 | contentType: RdfSerializationType.Trig, 28 | name: 'TriG', 29 | type: RdfSerializationType.Trig, 30 | vscodeLanguageId: 'trig', 31 | vscodeFileExtension: 'trig' 32 | }, 33 | { 34 | contentType: RdfSerializationType.NQuads, 35 | name: 'N-Quads', 36 | type: RdfSerializationType.NQuads, 37 | vscodeLanguageId: 'n-quads', 38 | vscodeFileExtension: 'nq' 39 | }, 40 | { 41 | contentType: RdfSerializationType.NTriples, 42 | name: 'N-Triples', 43 | type: RdfSerializationType.NTriples, 44 | vscodeLanguageId: 'ntriples', 45 | vscodeFileExtension: 'nt' 46 | }, 47 | /* { 48 | contentType: RdfSerializationType.N3, 49 | name: 'N3', 50 | type: RdfSerializationType.N3, 51 | },*/ 52 | /* 53 | { 54 | contentType: RdfSerializationType.RdfXML, 55 | name: 'RDF/XML', 56 | type: RdfSerializationType.RdfXML, 57 | 58 | } 59 | */ 60 | ]; 61 | 62 | 63 | export interface RdfFormat { 64 | contentType: string; 65 | name: string; 66 | type: RdfSerializationType; 67 | vscodeLanguageId: string; 68 | vscodeFileExtension: string; 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src-vscode/src/constant/update-event-type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Keep that in sync with the `updateEventType` in `src/vscode-webview/constant/update-event-type.ts` 3 | */ 4 | 5 | export const updateEventType = 'sketch.updateContent'; -------------------------------------------------------------------------------- /src-vscode/src/extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, commands, window, Uri } from 'vscode'; 2 | 3 | import { RdfPreviewPanel } from './rdf-preview-panel'; 4 | 5 | export function activate(context: ExtensionContext) { 6 | 7 | const rdfPreviewPanels = new Map(); 8 | 9 | const webViewDisposable = commands.registerCommand('rdf-sketch.openPreview', (uri: Uri) => { 10 | const rdfEditor = window.visibleTextEditors.find(editor => editor.document.uri.toString() === uri.toString()); 11 | if (!rdfEditor) { 12 | window.showErrorMessage('Please open an RDF file first'); 13 | return; 14 | } 15 | 16 | const uriString = uri.toString(); 17 | let rdfPreviewPanel = rdfPreviewPanels.get(uriString); 18 | 19 | if (!rdfPreviewPanel) { 20 | rdfPreviewPanel = new RdfPreviewPanel(context, rdfEditor); 21 | rdfPreviewPanels.set(uriString, rdfPreviewPanel); 22 | 23 | const disposeListener = rdfPreviewPanel.onDidDispose(() => { 24 | rdfPreviewPanels.delete(uriString); 25 | disposeListener.dispose(); 26 | }); 27 | } 28 | 29 | if (!rdfPreviewPanel.hasExistingPanel) { 30 | rdfPreviewPanel.createPanel(); 31 | } else { 32 | window.showInformationMessage('RDF Preview exists.'); 33 | } 34 | 35 | }); 36 | 37 | context.subscriptions.push(webViewDisposable); 38 | } 39 | 40 | // This method is called when your extension is deactivated 41 | export function deactivate() { } 42 | 43 | 44 | -------------------------------------------------------------------------------- /src-vscode/src/model/update-message.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Keep that in sync with the `UpdateMessage` in `src/vscode-webview/model/update-message.ts` 3 | */ 4 | export interface UpdateMessage { 5 | rdfString: string, 6 | contentType: string 7 | }; -------------------------------------------------------------------------------- /src-vscode/src/rdf-preview-panel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Uri, 3 | window, 4 | ViewColumn, 5 | TextDocument, 6 | workspace, 7 | TextDocumentChangeEvent, 8 | Webview, 9 | WebviewPanel, 10 | ExtensionContext, 11 | TextEditor, 12 | EventEmitter, 13 | Disposable, 14 | WebviewPanelOnDidChangeViewStateEvent 15 | } from "vscode"; 16 | 17 | import { rdfFormats } from "./constant/rdf-formats"; 18 | import { UpdateMessage } from "./model/update-message"; 19 | import { updateEventType } from "./constant/update-event-type"; 20 | 21 | export class RdfPreviewPanel { 22 | 23 | readonly viewType = 'rdfPreview'; 24 | 25 | #lastMessage: UpdateMessage | null = null; 26 | #panel: WebviewPanel | null = null; 27 | #context: ExtensionContext; 28 | #textEditor: TextEditor; 29 | 30 | #lastVisibility = false; 31 | #lastActive = false; 32 | #lastColumn: ViewColumn | undefined = undefined; 33 | 34 | readonly #onDidDispose = new EventEmitter(); 35 | readonly #disposables: Disposable[] = []; 36 | 37 | 38 | constructor(context: ExtensionContext, editor: TextEditor) { 39 | this.#context = context; 40 | this.#textEditor = editor; 41 | } 42 | 43 | get hasExistingPanel() { 44 | return this.#panel !== null; 45 | } 46 | 47 | get onDidDispose() { 48 | return this.#onDidDispose.event; 49 | } 50 | 51 | createPanel() { 52 | const column = window.activeTextEditor ? window.activeTextEditor.viewColumn : undefined; 53 | 54 | this.#panel = window.createWebviewPanel( 55 | this.viewType, 56 | 'RDF Sketch', 57 | { 58 | preserveFocus: false, 59 | viewColumn: column ? column + 1 : ViewColumn.One, 60 | }, 61 | { 62 | enableScripts: true, 63 | retainContextWhenHidden: true, 64 | } 65 | ); 66 | this.#lastActive = true; 67 | this.#lastVisibility = true; 68 | this.#lastColumn = this.#panel.viewColumn; 69 | 70 | workspace.onDidChangeTextDocument((textDocumentChangeEvent: TextDocumentChangeEvent) => { 71 | if (textDocumentChangeEvent.document !== this.#textEditor.document) { 72 | return; 73 | } 74 | this.updateWebviewContent(textDocumentChangeEvent.document, 'documentChange'); 75 | }); 76 | 77 | 78 | if (this.#textEditor) { 79 | this.updateWebview(this.#textEditor.document); 80 | } 81 | 82 | this.#disposables.push( 83 | this.#panel.onDidChangeViewState((e: WebviewPanelOnDidChangeViewStateEvent) => { 84 | const panel = e.webviewPanel; 85 | 86 | /** 87 | * I think this is a bug in vscode. 88 | * This condition is based on testing the event behavior and has not any real logic behind it. 89 | * It should update the panel only if it's detached from the editor (a separate widow). 90 | * Only resend the panel content if ... 91 | */ 92 | if (!this.#hasVisibilityChanged(panel.visible) && 93 | this.#hasActiveChanged(panel.active) && 94 | this.#hasColumnChanged(panel.viewColumn) && 95 | this.#lastMessage !== null) { 96 | this.#panel!.webview.postMessage({ type: 'updateContentVsCodeEvent', content: this.#lastMessage }); 97 | } 98 | 99 | this.#lastActive = panel.active; 100 | this.#lastVisibility = panel.visible; 101 | this.#lastColumn = panel.viewColumn; 102 | }) 103 | 104 | ); 105 | 106 | this.#disposables.push( 107 | this.#panel.onDidDispose(() => { 108 | this.dispose(); 109 | }) 110 | ); 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | } 119 | 120 | updateWebview(document: TextDocument) { 121 | const content = document.getText(); 122 | this.#panel!.webview.html = this.#getHtmlForWebview(this.#panel!.webview, this.#context.extensionUri, content); 123 | 124 | setTimeout(() => { 125 | this.updateWebviewContent(document, 'initial'); 126 | }, 1); 127 | }; 128 | 129 | updateWebviewContent = (document: TextDocument, reason: string) => { 130 | const rdfString = document.getText(); 131 | const languageId = document.languageId; 132 | const fileExtension = document.fileName.split('.').pop(); 133 | 134 | let rdfFormatForVscodeLanguage = rdfFormats.find((format) => format.vscodeLanguageId === languageId); 135 | 136 | if (rdfFormatForVscodeLanguage === undefined) { 137 | rdfFormatForVscodeLanguage = rdfFormats.find((format) => format.vscodeFileExtension === fileExtension); 138 | } 139 | 140 | if (!rdfFormatForVscodeLanguage) { 141 | window.showErrorMessage(`No RDF format found for language ${languageId} and file extension ${fileExtension}`); 142 | return; 143 | } 144 | 145 | const content: UpdateMessage = { 146 | rdfString, 147 | contentType: rdfFormatForVscodeLanguage.contentType 148 | }; 149 | this.#lastMessage = content; 150 | this.#panel!.webview.postMessage({ type: 'updateContentVsCodeEvent', content: content }); 151 | }; 152 | 153 | 154 | #getHtmlForWebview(webview: Webview, extensionUri: Uri, content: string): string { 155 | // Get resource paths 156 | const stylesUri = webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', 'assets', 'main.css')); 157 | const appUri = webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', 'assets', 'main.js')); 158 | // const vendorUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'js', 'chunk-vendors.js')); 159 | 160 | const nonce = this.#getNonce(); 161 | 162 | const themeClass = window.activeColorTheme.kind === 2 ? 'dark' : 'light'; 163 | 164 | return ` 165 | 166 | 167 | 168 | 169 | 170 | 171 | RDF Sketch preview 172 | 173 | 174 |
175 | 176 | 192 | 200 | 201 | `; 202 | } 203 | 204 | #getNonce(): string { 205 | let text = ''; 206 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 207 | for (let i = 0; i < 32; i++) { 208 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 209 | } 210 | return text; 211 | } 212 | 213 | dispose() { 214 | this.#panel = null; 215 | this.#onDidDispose.fire(); 216 | this.#onDidDispose.dispose(); 217 | while (this.#disposables.length) { 218 | const disposable = this.#disposables.pop(); 219 | if (disposable) { 220 | disposable.dispose(); 221 | } 222 | } 223 | } 224 | 225 | #hasColumnChanged(currentColumn: ViewColumn | undefined): Boolean { 226 | return this.#lastColumn !== currentColumn; 227 | } 228 | 229 | #hasVisibilityChanged(currentState: Boolean): Boolean { 230 | return this.#lastVisibility !== currentState; 231 | } 232 | 233 | #hasActiveChanged(currentState: Boolean): Boolean { 234 | return this.#lastActive !== currentState; 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /src-vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "target": "ES2022", 5 | "lib": [ 6 | "ES2022" 7 | ], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true /* enable all strict type-checking options */ 11 | /* Additional Checks */ 12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src-vscode/webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | //@ts-check 8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 9 | 10 | /** @type WebpackConfig */ 11 | const extensionConfig = { 12 | target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 13 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 14 | 15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 16 | output: { 17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'extension.js', 20 | libraryTarget: 'commonjs2' 21 | }, 22 | externals: { 23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 24 | // modules added here also need to be added in the .vscodeignore file 25 | }, 26 | resolve: { 27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 28 | extensions: ['.ts', '.js'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader' 38 | } 39 | ] 40 | } 41 | ] 42 | }, 43 | devtool: 'nosources-source-map', 44 | infrastructureLogging: { 45 | level: "log", // enables logging required for problem matchers 46 | }, 47 | }; 48 | module.exports = [ extensionConfig ]; -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 105 | 106 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import 'primeicons/primeicons.css'; 2 | @layer tailwind-base, primevue, tailwind-utilities; 3 | 4 | @layer tailwind-base { 5 | @tailwind base; 6 | } 7 | 8 | @layer tailwind-utilities { 9 | @tailwind components; 10 | @tailwind utilities; 11 | } 12 | 13 | body { 14 | height: 100vh; 15 | max-height: 100vh; 16 | overflow: hidden; 17 | margin: 8px; 18 | font-family: 19 | 'Helvetica Neue', 20 | sans-serif; 21 | 22 | } 23 | 24 | a { 25 | text-decoration: none; 26 | } -------------------------------------------------------------------------------- /src/components/GraphView.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 164 | 165 | 194 | 195 | -------------------------------------------------------------------------------- /src/components/RdfEditor.vue: -------------------------------------------------------------------------------- 1 | 122 | 123 | 132 | 133 | 147 | -------------------------------------------------------------------------------- /src/components/RdfTerm.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 39 | 53 | -------------------------------------------------------------------------------- /src/components/SPOSearch.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 128 | 129 | 151 | -------------------------------------------------------------------------------- /src/components/configuration/AppConfiguration.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 25 | -------------------------------------------------------------------------------- /src/components/graph/floating-edge/FloatingEdge.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/graph/floating-edge/floating-edge-utils.ts: -------------------------------------------------------------------------------- 1 | import type { Edge, GraphNode, XYPosition } from '@vue-flow/core' 2 | import { MarkerType, Position } from '@vue-flow/core' 3 | 4 | // this helper function returns the intersection point 5 | // of the line between the center of the intersectionNode and the target node 6 | function getNodeIntersection(intersectionNode: GraphNode, targetNode: GraphNode): XYPosition { 7 | // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a 8 | const { 9 | dimensions: { width: intersectionNodeWidth, height: intersectionNodeHeight }, 10 | computedPosition: intersectionNodePosition, 11 | } = intersectionNode 12 | const targetPosition = targetNode.computedPosition 13 | 14 | const w = intersectionNodeWidth / 2 15 | const h = intersectionNodeHeight / 2 16 | 17 | const x2 = intersectionNodePosition.x + w 18 | const y2 = intersectionNodePosition.y + h 19 | const x1 = targetPosition.x + w 20 | const y1 = targetPosition.y + h 21 | 22 | const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h) 23 | const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h) 24 | const a = 1 / (Math.abs(xx1) + Math.abs(yy1)) 25 | const xx3 = a * xx1 26 | const yy3 = a * yy1 27 | const x = w * (xx3 + yy3) + x2 28 | const y = h * (-xx3 + yy3) + y2 29 | 30 | return { x, y } 31 | } 32 | 33 | // returns the position (top,right,bottom or right) passed node compared to the intersection point 34 | function getEdgePosition(node: GraphNode, intersectionPoint: XYPosition) { 35 | const n = { ...node.computedPosition, ...node.dimensions } 36 | const nx = Math.round(n.x) 37 | const ny = Math.round(n.y) 38 | const px = Math.round(intersectionPoint.x) 39 | const py = Math.round(intersectionPoint.y) 40 | 41 | if (px <= nx + 1) { 42 | return Position.Left 43 | } 44 | if (px >= nx + n.width - 1) { 45 | return Position.Right 46 | } 47 | if (py <= ny + 1) { 48 | return Position.Top 49 | } 50 | if (py >= n.y + n.height - 1) { 51 | return Position.Bottom 52 | } 53 | 54 | return Position.Top 55 | } 56 | 57 | // returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge 58 | export function getEdgeParams(source: GraphNode, target: GraphNode) { 59 | const sourceIntersectionPoint = getNodeIntersection(source, target) 60 | const targetIntersectionPoint = getNodeIntersection(target, source) 61 | 62 | const sourcePos = getEdgePosition(source, sourceIntersectionPoint) 63 | const targetPos = getEdgePosition(target, targetIntersectionPoint) 64 | 65 | return { 66 | sx: sourceIntersectionPoint.x, 67 | sy: sourceIntersectionPoint.y, 68 | tx: targetIntersectionPoint.x, 69 | ty: targetIntersectionPoint.y, 70 | sourcePos, 71 | targetPos, 72 | } 73 | } 74 | 75 | export function createElements() { 76 | const win = typeof window !== 'undefined' ? window : ({} as any) 77 | const elements = [] 78 | const center = { x: win.innerWidth / 2, y: win.innerHeight / 2 } 79 | 80 | elements.push({ id: 'target', label: 'Target', position: center }) 81 | 82 | for (let i = 0; i < 8; i++) { 83 | const degrees = i * (360 / 8) 84 | const radians = degrees * (Math.PI / 180) 85 | const x = 250 * Math.cos(radians) + center.x 86 | const y = 250 * Math.sin(radians) + center.y 87 | 88 | elements.push({ id: `${i}`, label: 'Source', position: { x, y } }) 89 | 90 | elements.push({ 91 | id: `edge-${i}`, 92 | target: 'target', 93 | source: `${i}`, 94 | type: 'floating', 95 | markerEnd: MarkerType.Arrow, 96 | markerStart: { 97 | id: 'markerstart', 98 | type: MarkerType.Arrow, 99 | orient: 'auto-start-reverse', 100 | }, 101 | } as Edge) 102 | } 103 | 104 | return elements 105 | } -------------------------------------------------------------------------------- /src/components/graph/resource-node/ResourceNode.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 77 | 78 | 79 | 80 | 81 | 82 | 160 | -------------------------------------------------------------------------------- /src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /src/components/share-button/ShareButton.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 81 | 82 | 105 | -------------------------------------------------------------------------------- /src/constant/local-storage-keys.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Local storage keys 3 | * All data stored to local storage is prefixed with `com.zazuko.sketch`. 4 | * 5 | * @module constants/local-storage-keys 6 | * @preferred 7 | */ 8 | export const localStoragePrefix = 'com.zazuko.sketch'; 9 | export const localStorageKeyText = `${localStoragePrefix}.text`; 10 | export const localStorageKeyFormat = `${localStoragePrefix}.format`; 11 | export const localStorageKeyPrefixList = `${localStoragePrefix}.prefix`; -------------------------------------------------------------------------------- /src/constant/min-max.ts: -------------------------------------------------------------------------------- 1 | export const MAX_TRIPLES_TO_BE_STORED = 5000; -------------------------------------------------------------------------------- /src/constant/rdf-format.ts: -------------------------------------------------------------------------------- 1 | export enum RdfSerializationType { 2 | JsonLD = 'application/ld+json', 3 | Trig = 'application/trig', 4 | NQuads = 'application/n-quads', 5 | NTriples = 'application/n-triples', 6 | // N3 = 'text/n3', 7 | Turtle = 'text/turtle', 8 | RdfXML = 'application/rdf+xml', 9 | } 10 | 11 | export const rdfFormats: RdfFormat[] = [ 12 | { 13 | contentType: RdfSerializationType.Turtle, 14 | name: 'Turtle', 15 | type: RdfSerializationType.Turtle, 16 | }, 17 | { 18 | contentType: RdfSerializationType.JsonLD, 19 | name: 'JSON-LD', 20 | type: RdfSerializationType.JsonLD, 21 | }, 22 | { 23 | contentType: RdfSerializationType.Trig, 24 | name: 'TriG', 25 | type: RdfSerializationType.Trig, 26 | }, 27 | { 28 | contentType: RdfSerializationType.NQuads, 29 | name: 'N-Quads', 30 | type: RdfSerializationType.NQuads, 31 | }, 32 | { 33 | contentType: RdfSerializationType.NTriples, 34 | name: 'N-Triples', 35 | type: RdfSerializationType.NTriples, 36 | }, 37 | /* { 38 | contentType: RdfSerializationType.N3, 39 | name: 'N3', 40 | type: RdfSerializationType.N3, 41 | },*/ 42 | 43 | { 44 | contentType: RdfSerializationType.RdfXML, 45 | name: 'RDF/XML', 46 | type: RdfSerializationType.RdfXML, 47 | } 48 | ]; 49 | 50 | 51 | export interface RdfFormat { 52 | contentType: string; 53 | name: string; 54 | type: RdfSerializationType; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/layout/use-layout.ts: -------------------------------------------------------------------------------- 1 | import ELK from 'elkjs/lib/elk.bundled.js'; 2 | 3 | import { type Node, type Edge } from '@vue-flow/core' 4 | 5 | const headerHeight = 76; 6 | const rowHeight = 52; 7 | 8 | /** 9 | * Composable to run the layout algorithm on the graph. 10 | * It uses the `dagre` library to calculate the layout of the nodes and edges. 11 | */ 12 | export function useLayout() { 13 | 14 | async function elkLayout(nodes: Node[], edges: Edge[]) { 15 | 16 | const options = { 17 | "algorithm": "mrtree", // MRTREE algorithm for tree layout 18 | "org.eclipse.elk.direction": "RIGHT", // Layout direction (can be UP, DOWN, LEFT, RIGHT) 19 | "org.eclipse.elk.spacing.nodeNode": "400", // Space between nodes 20 | "org.eclipse.elk.spacing.edgeEdge": "20", // Space between edges 21 | "org.eclipse.elk.spacing.edgeNode": "30", // Space between nodes and edges 22 | "org.eclipse.elk.mrtree.spacing.level": "500", // Space between levels in the tree 23 | "org.eclipse.elk.mrtree.compaction.strategy": "DOWN", // Compaction strategy 24 | "org.eclipse.elk.mrtree.nodePlacement.strategy": "SIMPLE", // Simple node placement strategy 25 | "org.eclipse.elk.mrtree.nodePlacement.bk.fixedAlignment": "BALANCED", // Balanced alignment for nodes 26 | }; 27 | 28 | const elkNodes: any[] = nodes.map((node) => { 29 | return { 30 | id: node.id, 31 | width: 1000, 32 | height: headerHeight + rowHeight * node.data.resource.properties.length, 33 | labels: [{ text: node.data.resource.name }], 34 | properties: node.data.resource.properties.map((property) => { 35 | return { 36 | id: property.id, 37 | width: 1000, 38 | height: rowHeight, 39 | labels: [{ text: property.name }], 40 | } 41 | }), 42 | } 43 | }); 44 | 45 | const elkEdges = edges.map((edge) => { 46 | return { 47 | id: `${edge.source}-${edge.target}`, 48 | sources: [edge.source], 49 | targets: [edge.target], 50 | } 51 | }); 52 | 53 | const graph = { 54 | id: 'root', 55 | layoutOptions: options, 56 | children: elkNodes, 57 | edges: elkEdges, 58 | }; 59 | 60 | 61 | 62 | const elk = new ELK(); 63 | 64 | return elk 65 | .layout(graph as any) 66 | .then((layoutedGraph) => { 67 | const nodesWithPosition = nodes.map(node => { 68 | const elkNode = layoutedGraph.children.find((n) => n.id === node.id) 69 | node.position = { x: elkNode.x, y: elkNode.y } 70 | return node; 71 | }); 72 | 73 | for (const edge of edges) { 74 | const sourceNode = nodesWithPosition.find((node) => node.id === edge.source) 75 | const targetNode = nodesWithPosition.find((node) => node.id === edge.target) 76 | 77 | if (sourceNode.position.x < targetNode.position.x) { 78 | const handlePosition = `right`; 79 | const currentSourceHandle = edge.sourceHandle; 80 | if (!currentSourceHandle.endsWith(handlePosition)) { 81 | // remove the word right from the end of the string 82 | edge.sourceHandle = currentSourceHandle.slice(0, -5) 83 | edge.sourceHandle = `${edge.sourceHandle}${handlePosition}` 84 | 85 | } 86 | } else { 87 | const handlePosition = `left`; 88 | const currentSourceHandle = edge.sourceHandle; 89 | if (!currentSourceHandle.endsWith(handlePosition)) { 90 | // remove the word left from the end of the string 91 | edge.sourceHandle = currentSourceHandle.slice(0, -5) 92 | edge.sourceHandle = `${edge.sourceHandle}${handlePosition}` 93 | 94 | } 95 | } 96 | 97 | 98 | 99 | } 100 | return { 101 | nodes: nodesWithPosition, 102 | edges: edges, 103 | } 104 | }) 105 | .catch(console.error); 106 | 107 | } 108 | 109 | return { elkLayout } 110 | } 111 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | 3 | import { createApp } from 'vue'; 4 | 5 | import PrimeVue from 'primevue/config'; 6 | 7 | import Aura from '@primeuix/themes/aura'; 8 | import App from './App.vue'; 9 | 10 | import { useUrlSearchParams } from '@vueuse/core'; 11 | import { rdfFormats, RdfSerializationType } from './constant/rdf-format'; 12 | import { localStorageKeyFormat, localStorageKeyText } from './constant/local-storage-keys'; 13 | 14 | const params = useUrlSearchParams('hash-params') 15 | 16 | const app = createApp(App); 17 | 18 | app.use(PrimeVue, { 19 | theme: { 20 | preset: Aura 21 | }, 22 | options: { 23 | darkModeSelector: '.my-app-dark', 24 | cssLayer: false 25 | } 26 | }); 27 | 28 | writeRdfFromUrlToLocalStorage(); 29 | 30 | app.mount('#app') 31 | 32 | 33 | /** 34 | * This is used for "Share" functionality. 35 | * It reads the RDF and format from the URL and writes it to the local storage. 36 | */ 37 | function writeRdfFromUrlToLocalStorage(): void { 38 | if (params.rdf) { 39 | const rdfText = (Array.isArray(params.rdf) ? params.rdf[0] : params.rdf) ?? ''; 40 | const rdfFormat = ((Array.isArray(params.format) ? params.format[0] : params.format) ?? RdfSerializationType.Turtle).replace(' ', '+'); 41 | if (rdfText.length > 0 && rdfFormats.find(f => f.type === rdfFormat)) { 42 | localStorage.setItem(localStorageKeyText, rdfText); 43 | localStorage.setItem(localStorageKeyFormat, rdfFormat); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/model/link.model.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Link { 3 | source: string, 4 | target: string, 5 | sourceProperty: string, 6 | label: string, 7 | } -------------------------------------------------------------------------------- /src/model/resource.model.ts: -------------------------------------------------------------------------------- 1 | import TermSet from "@rdfjs/term-set"; 2 | import type { Term } from "@rdfjs/types"; 3 | 4 | export interface Resource { 5 | id: string, 6 | name: string, 7 | term: Term, 8 | properties: Property[], 9 | avatars: ResourceAvatar[], 10 | } 11 | 12 | export interface Property { 13 | id: string, 14 | name: string, 15 | values: TermSet, 16 | } 17 | 18 | export interface ResourceAvatar { 19 | label: string; 20 | color: string; 21 | iri: string; 22 | } -------------------------------------------------------------------------------- /src/model/tab.model.ts: -------------------------------------------------------------------------------- 1 | export interface Tab { 2 | content: string, 3 | format: string 4 | id: string, 5 | isEditing?: boolean, 6 | label: string 7 | } -------------------------------------------------------------------------------- /src/rdf/environment.ts: -------------------------------------------------------------------------------- 1 | import rdf from '@zazuko/env'; 2 | 3 | import formats from '@rdfjs/formats'; 4 | 5 | 6 | rdf.formats.import(formats); 7 | 8 | export const rdfEnvironment = rdf; 9 | -------------------------------------------------------------------------------- /src/rdf/prefix-map.ts: -------------------------------------------------------------------------------- 1 | import type { RdfPrefix } from "@/components/RdfEditor.vue"; 2 | 3 | 4 | class SketchPrefixMap { 5 | #prefixes: Record = {}; 6 | 7 | update(prefixList: RdfPrefix[]) { 8 | this.#prefixes = {}; 9 | prefixList.forEach(prefix => { 10 | this.#prefixes[prefix.prefix] = prefix.uri; 11 | }); 12 | } 13 | 14 | get prefixes() { 15 | return this.#prefixes; 16 | } 17 | } 18 | 19 | export const prefixMap = new SketchPrefixMap(); -------------------------------------------------------------------------------- /src/rdf/shrink-term.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { Term } from '@rdfjs/types'; 3 | import { shrink } from '@zazuko/prefixes' 4 | 5 | import { prefixMap } from '@/rdf/prefix-map'; 6 | 7 | 8 | /** 9 | * Shrink a term to a prefixed string 10 | * 11 | * It uses the prefixes from the App wide used prefixMap 12 | * 13 | * @param term 14 | * @returns a prefixed string or the original value 15 | */ 16 | export function shrinkTerm(term: Term): string { 17 | if (term.termType === 'NamedNode') { 18 | const s = shrink(term.value, prefixMap.prefixes); 19 | return s ? s : term.value 20 | } 21 | return term.value 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/resources-utils.ts: -------------------------------------------------------------------------------- 1 | import TermSet from "@rdfjs/term-set" 2 | import type { Link } from "./model/link.model" 3 | import type { Resource } from "./model/resource.model" 4 | import { rdfEnvironment } from './rdf/environment'; 5 | import { shrinkTerm } from "./rdf/shrink-term"; 6 | import type { Dataset } from "@rdfjs/types"; 7 | 8 | 9 | export function resourcesFromDataset(dataset: Dataset): Resource[] { 10 | const extractedSubjects = [...dataset].map(quad => quad.subject) 11 | const extractedObject = [...dataset].filter( 12 | quad => !quad.predicate.equals(rdfEnvironment.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'))).map(quad => quad.object).filter(o => o.termType === "BlankNode" || o.termType === "NamedNode") 13 | 14 | const nodeSet = new TermSet([...extractedSubjects, ...extractedObject]) 15 | 16 | const resources = [...nodeSet].map(node => { 17 | const quads = dataset.match(node); 18 | const rdfClasses = [...quads].filter(quad => quad.predicate.equals(rdfEnvironment.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'))).map(quad => quad.object.value); 19 | const avatarsWithDuplicates = rdfClasses.map(rdfClass => { 20 | return { 21 | label: mapTypeToLabel(rdfClass), 22 | color: mapTypeToColor(rdfClass), 23 | iri: rdfClass 24 | } 25 | }); 26 | const avatars = avatarsWithDuplicates.filter((avatar, index) => avatarsWithDuplicates.findIndex(a => a.iri === avatar.iri) === index); 27 | const properties = [...quads].reduce((acc, { predicate, object }) => { 28 | if (!acc.has(predicate.value)) { 29 | const property = { 30 | id: predicate.value, 31 | term: predicate, 32 | name: shrinkTerm(predicate), 33 | values: new TermSet(), 34 | } 35 | acc.set(predicate.value, property) 36 | } 37 | acc.get(predicate.value).values.add(object) 38 | return acc 39 | }, new Map()) 40 | 41 | // order properties by name but rdf:type first 42 | const orderedProperties = [...properties.values()].sort((a, b) => { 43 | if (a.name === 'rdf:type') return -1; 44 | if (b.name === 'rdf:type') return 1; 45 | return a.name.localeCompare(b.name); 46 | }) 47 | 48 | return { 49 | id: node.value === '' ? '_:nobody' : node.value, 50 | term: node, 51 | name: shrinkTerm(node), 52 | properties: orderedProperties, 53 | avatars 54 | } as Resource 55 | }); 56 | return resources 57 | } 58 | 59 | 60 | export function linksFromResources(resources: Resource[]): Link[] { 61 | const resourceIds = new TermSet(resources.map(resource => { 62 | return resource.term 63 | })) 64 | 65 | const links = resources 66 | .flatMap(resource => resource.properties.map(property => { 67 | return ({ ...property, resource }) 68 | })) 69 | .reduce((links: Link[], property) => { 70 | property.values.forEach((value) => { 71 | const source = property.resource.term 72 | const target = value 73 | if (resourceIds.has(target)) { 74 | links.push({ 75 | source: source.value, 76 | target: target.value, 77 | sourceProperty: property.id, 78 | label: property.name, 79 | }) 80 | } 81 | }) 82 | return links 83 | }, 84 | []); 85 | return links 86 | } 87 | 88 | export const colorPalette = ["#b30000", "#7c1158", "#4421af", "#1a53ff", "#0d88e6", "#00b7c7", "#5ad45a", "#8be04e", "#ebdc78"]; 89 | 90 | export function mapTypeToColor(typeIri: string): string { 91 | const typeHash = Array.from(typeIri).reduce((hash, char) => { 92 | return char.charCodeAt(0) + ((hash << 5) - hash); 93 | }, 0); 94 | 95 | const colorIndex = Math.abs(typeHash) % colorPalette.length; 96 | return colorPalette[colorIndex]; 97 | } 98 | function mapTypeToLabel(typeIri: string): string { 99 | const iriParts = typeIri.split(/[#\/]/).filter(Boolean); 100 | const lastPart = iriParts[iriParts.length - 1]; 101 | 102 | const cc = lastPart.replace(/[a-z]/g, ''); 103 | if (cc.length > 1) { 104 | 105 | return cc.slice(0, 2); 106 | } 107 | const label = lastPart.replace(/([a-z])([A-Z])/g, '$1 $2') 108 | .split(/[^a-zA-Z0-9]/) 109 | .filter(Boolean) 110 | .map(part => part[0].toUpperCase() + (part[1] ? part[1].toLowerCase() : '')) 111 | .join('') 112 | .slice(0, 2); 113 | 114 | return label; 115 | } -------------------------------------------------------------------------------- /src/vscode-webview/AppVscode.vue: -------------------------------------------------------------------------------- 1 | 115 | 116 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/vscode-webview/constant/update-event-type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Keep that in sync with the `updateEventType` in src-vscode/src/constant/update-event-type.ts`` 3 | */ 4 | 5 | export const updateEventType = 'sketch.updateContent'; 6 | 7 | -------------------------------------------------------------------------------- /src/vscode-webview/main.ts: -------------------------------------------------------------------------------- 1 | import '../assets/main.css' 2 | 3 | import { createApp } from 'vue'; 4 | 5 | import PrimeVue from 'primevue/config'; 6 | 7 | import Aura from '@primevue/themes/aura'; 8 | import App from './AppVscode.vue'; 9 | 10 | 11 | 12 | const app = createApp(App); 13 | 14 | app.use(PrimeVue, { 15 | theme: { 16 | preset: Aura, 17 | options: { 18 | darkModeSelector: '.vscode-dark', 19 | cssLayer: false 20 | } 21 | }, 22 | 23 | }); 24 | 25 | 26 | app.mount('#app') -------------------------------------------------------------------------------- /src/vscode-webview/model/update-message.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Keep that in sync with the `UpdateMessage` in 'src-vscode/src/model/update-message.ts' 3 | */ 4 | export interface UpdateMessage { 5 | rdfString: string, 6 | contentType: string 7 | }; -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": [ 4 | "env.d.ts", 5 | "src/**/*", 6 | "src/**/*.vue" 7 | ], 8 | "exclude": [ 9 | "src/**/__tests__/*" 10 | ], 11 | "compilerOptions": { 12 | "composite": true, 13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 14 | "baseUrl": ".", 15 | "paths": { 16 | "@/*": [ 17 | "./src/*" 18 | ] 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "noEmit": true, 13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 14 | 15 | "module": "ESNext", 16 | "moduleResolution": "Bundler", 17 | "types": ["node"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.main.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import { VitePWA } from 'vite-plugin-pwa' 6 | 7 | import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill' 8 | import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill' 9 | 10 | import rollupNodePolyFill from 'rollup-plugin-node-polyfills' 11 | import inject from '@rollup/plugin-inject'; 12 | 13 | // https://vitejs.dev/config/ 14 | export default defineConfig({ 15 | define: { 16 | global: 'window', 17 | }, 18 | plugins: [ 19 | vue({ 20 | template: { 21 | compilerOptions: { 22 | // treat any tag that starts with rdf- as custom elements 23 | isCustomElement: tag => tag.startsWith('rdf-') 24 | } 25 | } 26 | }), 27 | VitePWA({ 28 | registerType: 'autoUpdate', 29 | filename: 'service-worker.js', 30 | workbox: { 31 | maximumFileSizeToCacheInBytes: 5 * 1024 ** 2, // 5 MB or set to something else 32 | globPatterns: ['**/*.{js,css,html,woff,woff2,svg,webmanifest,ico,png}'], 33 | 34 | } 35 | }), 36 | ], 37 | resolve: { 38 | alias: { 39 | '@': fileURLToPath(new URL('./src', import.meta.url)), 40 | stream: 'readable-stream', 41 | util: 'util', 42 | } 43 | }, 44 | optimizeDeps: { 45 | esbuildOptions: { 46 | plugins: [ 47 | NodeGlobalsPolyfillPlugin({ 48 | process: true, 49 | buffer: true, 50 | }) as any, 51 | NodeModulesPolyfillPlugin() 52 | ], 53 | }, 54 | }, 55 | build: { 56 | rollupOptions: { 57 | plugins: [ 58 | rollupNodePolyFill() as any, 59 | inject({ 60 | process: 'process', // Inject process global 61 | Buffer: ['buffer', 'Buffer'], // Inject Buffer global 62 | }), 63 | ], 64 | input: { 65 | main: fileURLToPath(new URL('./index.html', import.meta.url)) 66 | } 67 | }, 68 | }, 69 | }) 70 | -------------------------------------------------------------------------------- /vite.config.vscode.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill' 7 | import rollupNodePolyFill from 'rollup-plugin-node-polyfills' 8 | import inject from '@rollup/plugin-inject'; 9 | 10 | // https://vitejs.dev/config/ 11 | export default defineConfig({ 12 | define: { 13 | global: 'window', 14 | }, 15 | plugins: [ 16 | vue({ 17 | template: { 18 | compilerOptions: { 19 | } 20 | } 21 | }), 22 | ], 23 | resolve: { 24 | alias: { 25 | '@': fileURLToPath(new URL('./src', import.meta.url)), 26 | stream: 'readable-stream', 27 | util: 'util', 28 | } 29 | }, 30 | optimizeDeps: { 31 | esbuildOptions: { 32 | plugins: [ 33 | NodeGlobalsPolyfillPlugin({ 34 | process: true, 35 | buffer: true, 36 | }) as any, 37 | ], 38 | }, 39 | }, 40 | build: { 41 | rollupOptions: { 42 | plugins: [rollupNodePolyFill() as any, 43 | inject({ 44 | process: 'process', // Inject process global 45 | Buffer: ['buffer', 'Buffer'], // Inject Buffer global 46 | }),], 47 | input: { 48 | main: fileURLToPath(new URL('./vscode/index.html', import.meta.url)) 49 | }, 50 | output: { 51 | entryFileNames: `assets/[name].js`, 52 | chunkFileNames: `assets/[name].js`, 53 | assetFileNames: `assets/[name].[ext]` 54 | } 55 | }, 56 | outDir: 'src-vscode/media', // specify the target folder for the build 57 | }, 58 | }) 59 | -------------------------------------------------------------------------------- /vscode/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | VSCode Web View 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 352 | 353 | 354 | --------------------------------------------------------------------------------