├── .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 
24 |
25 | 4. A new tab will open with the visualization
26 |
27 | # Demo
28 | The preview is updated on typing.
29 | 
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 | 
33 |
34 | You can search by Subject, Predicate, or Object and click to move to the Node. In order to find nodes.
35 | 
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 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | Sketch
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | Sketch is a simple yet powerful tool for visualizing RDF graphs. It allows you to:
169 |
170 |
171 |
172 | Traverse and Explore: Seamlessly navigate through your RDF graphs.
173 |
174 |
175 | Search with Ease:Quickly locate nodes and connections.
176 |
177 |
178 | Interact Intuitively: Click edges and Objects (SPO) to navigate.
179 |
180 |
181 | You can find more tools and resources at
Zazuko.com .
182 |
183 |
184 |
185 |
186 |
187 |
188 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
164 |
165 |
194 |
195 |
--------------------------------------------------------------------------------
/src/components/RdfEditor.vue:
--------------------------------------------------------------------------------
1 |
122 |
123 |
124 |
125 |
126 |
129 |
130 |
131 |
132 |
133 |
147 |
--------------------------------------------------------------------------------
/src/components/RdfTerm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ displayValue }}
5 | [{{ displayValue }}]
6 | {{ displayValue }}@{{ language }}
7 |
8 |
9 |
10 |
11 |
39 |
53 |
--------------------------------------------------------------------------------
/src/components/SPOSearch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ data.subject }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{ data.predicate }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{ data.object }}
38 |
39 |
40 | {{ data.object }}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {{ data.context }}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
128 |
129 |
151 |
--------------------------------------------------------------------------------
/src/components/configuration/AppConfiguration.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
hello world
5 |
6 |
7 |
8 |
9 |
19 |
25 |
--------------------------------------------------------------------------------
/src/components/graph/floating-edge/FloatingEdge.vue:
--------------------------------------------------------------------------------
1 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/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 |
34 |
35 |
36 |
37 |
51 |
52 |
53 |
58 |
59 | {{ property.name }}
60 |
61 |
62 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
160 |
--------------------------------------------------------------------------------
/src/components/icons/IconCommunity.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/components/icons/IconDocumentation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/components/icons/IconEcosystem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/components/icons/IconSupport.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/components/icons/IconTooling.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/share-button/ShareButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
{{ shareUrl }}
9 |
...
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
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 |
--------------------------------------------------------------------------------