From 60e11ad62f45457d5423b70efdda2fb148dd627c Mon Sep 17 00:00:00 2001 From: tarn Date: Tue, 26 May 2026 11:58:22 +0200 Subject: [PATCH 1/8] datasetStore --- frontend/.gitignore | 2 + frontend/licenses-third-party.js | 14 +- frontend/openapi-ts.config.ts | 28 + frontend/package-lock.json | 479 +++++++++++++++++- frontend/package.json | 4 +- frontend/src/lib/GraphExport.svelte | 2 +- frontend/src/lib/api/apiDatasetUtils.js | 21 - frontend/src/lib/api/backend.js | 26 - frontend/src/lib/api/hey-api.ts | 25 + .../DatasetAndGraphSelection.svelte | 73 +-- .../lib/components/FileSelectButton.svelte | 2 +- .../validity-rules/validityFunctions.js | 10 +- .../svelteflow/svelteFlowWrapper.svelte | 8 +- frontend/src/lib/stores/DatasetStore.ts | 185 +++++++ frontend/src/lib/stores/GraphStore.ts | 173 +++++++ frontend/src/routes/+layout.svelte | 8 +- .../src/routes/DatasetDeleteDialog.svelte | 5 +- frontend/src/routes/ImportDialog.svelte | 18 +- frontend/src/routes/NamespacesDialog.svelte | 8 +- frontend/src/routes/NewGraphDialog.svelte | 23 +- frontend/src/routes/Searchbar.svelte | 2 +- frontend/src/routes/SnapshotDialog.svelte | 18 +- frontend/src/routes/changelog/Changes.svelte | 4 +- .../src/routes/changelog/Navigation.svelte | 23 +- .../mainpage/classEditor/classEditor.svelte | 7 +- .../CustomDiagramButton.svelte | 8 +- .../packageNavigation/DatasetSection.svelte | 5 +- .../packageNavigation/build-nav-object.js | 22 +- .../AddToDatasetDiagramDialog.svelte | 2 +- .../AddToGraphDiagramDialog.svelte | 2 +- .../routes/mainpage/renderingWrapper.svelte | 8 +- .../src/routes/shacl/SHACLUploadDialog.svelte | 2 +- 32 files changed, 1003 insertions(+), 214 deletions(-) create mode 100644 frontend/openapi-ts.config.ts create mode 100644 frontend/src/lib/api/hey-api.ts create mode 100644 frontend/src/lib/stores/DatasetStore.ts create mode 100644 frontend/src/lib/stores/GraphStore.ts diff --git a/frontend/.gitignore b/frontend/.gitignore index e1c5b032..daaadd1d 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -6,6 +6,8 @@ node_modules /.svelte-kit /build +src/lib/api/generated + # OS .DS_Store Thumbs.db diff --git a/frontend/licenses-third-party.js b/frontend/licenses-third-party.js index 1744573c..fae266d7 100644 --- a/frontend/licenses-third-party.js +++ b/frontend/licenses-third-party.js @@ -15,9 +15,9 @@ * */ -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; const __filepath = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filepath); @@ -53,7 +53,7 @@ function generateLicenseContent() { .replace(/\.git$/, ""); packages.push({ - name: pkg.name, + name: pkg.label, version: pkg.version, license, reference, @@ -65,9 +65,9 @@ function generateLicenseContent() { let markdown = "# Third-Party Licenses\n\n"; - for (const pkg of packages.sort((a, b) => a.name.localeCompare(b.name))) { - markdown += `### ${pkg.name}\n`; - markdown += `- **Package:** ${pkg.name}\n`; + for (const pkg of packages.toSorted((a, b) => a.label.localeCompare(b.label))) { + markdown += `### ${pkg.label}\n`; + markdown += `- **Package:** ${pkg.label}\n`; markdown += `- **Version:** ${pkg.version}\n`; markdown += `- **License:** ${pkg.license}\n`; if (pkg.reference) { diff --git a/frontend/openapi-ts.config.ts b/frontend/openapi-ts.config.ts new file mode 100644 index 00000000..50613709 --- /dev/null +++ b/frontend/openapi-ts.config.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineConfig } from "@hey-api/openapi-ts"; + +export default defineConfig({ + input: "http://localhost:8080/v3/api-docs", + output: "src/lib/api/generated", + plugins: [ + { + name: "@hey-api/client-fetch", + runtimeConfigPath: "./src/lib/api/hey-api", + }, + ], +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f241d7d0..d63f0706 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,6 +31,7 @@ "@eslint/compat": "^2.0.0", "@eslint/js": "^10.0.0", "@faker-js/faker": "^10.0.0", + "@hey-api/openapi-ts": "0.97.1", "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/kit": "^2.5.27", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -1123,6 +1124,120 @@ "node": ">=6" } }, + "node_modules/@hey-api/codegen-core": { + "version": "0.8.1", + "integrity": "sha512-Iciv2vUCJTW9lWM/ROvyZLblmcbYJHPuXfzb1SzeDVVn4xEXu2ilLU1pq3fn+09FZ/Y0P7VyvRE47UDU6om8xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/types": "0.1.4", + "ansi-colors": "4.1.3", + "c12": "3.3.4", + "color-support": "1.1.3" + }, + "engines": { + "node": ">=22.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/json-schema-ref-parser": { + "version": "1.4.2", + "integrity": "sha512-ZhCFSKI2ipZHEbgmtUHdyddvRU3wJ4elgCfYUC7T7hZa4EivSrVflTQf2w+v3TuaYxR1Y2V2kq3otqTttrrK8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "7.1.3", + "@types/json-schema": "7.0.15", + "js-yaml": "4.1.1" + }, + "engines": { + "node": ">=22.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.97.1", + "integrity": "sha512-LksUJeXAqwf6OhcCCr3/B4YjnBs5rqSqjDUKMBvkgp4OhaCQiJrOvntctFxdnugy8jUojP4yi/eJf5xYzcYzCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/codegen-core": "0.8.1", + "@hey-api/json-schema-ref-parser": "1.4.2", + "@hey-api/shared": "0.4.3", + "@hey-api/spec-types": "0.2.0", + "@hey-api/types": "0.1.4", + "@lukeed/ms": "2.0.2", + "ansi-colors": "4.1.3", + "color-support": "1.1.3", + "commander": "14.0.3", + "get-tsconfig": "4.14.0" + }, + "bin": { + "openapi-ts": "bin/run.js" + }, + "engines": { + "node": ">=22.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": ">=5.5.3 || >=6.0.0 || 6.0.1-rc" + } + }, + "node_modules/@hey-api/openapi-ts/node_modules/commander": { + "version": "14.0.3", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@hey-api/shared": { + "version": "0.4.3", + "integrity": "sha512-3tHfZNXgGOt+3P3Kq9cvqmZ9i7e3jtrkip1uDpZTX1+hTNboHhYdjxnT8AbrDuvslTaQHoAOlP4/iCDdzd9Jag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/codegen-core": "0.8.1", + "@hey-api/json-schema-ref-parser": "1.4.2", + "@hey-api/spec-types": "0.2.0", + "@hey-api/types": "0.1.4", + "ansi-colors": "4.1.3", + "cross-spawn": "7.0.6", + "open": "11.0.0", + "semver": "7.7.4" + }, + "engines": { + "node": ">=22.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/spec-types": { + "version": "0.2.0", + "integrity": "sha512-ibQ8Is7evMavzr8GNyJCcTg975d8DpaMUyLmOrQ85UBdy1l6t1KuRAwgChAbesJsIlNV6gjmlXruWyegDX18Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/types": "0.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/types": { + "version": "0.1.4", + "integrity": "sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg==", + "dev": true, + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.2", "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", @@ -1248,6 +1363,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, "node_modules/@lezer/common": { "version": "1.5.2", "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", @@ -1269,6 +1390,15 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", @@ -2910,6 +3040,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", @@ -2932,6 +3071,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/aria-query": { "version": "5.3.1", "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", @@ -3138,6 +3283,94 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "5.0.0", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/confbox": { + "version": "0.2.4", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/c12/node_modules/pkg-types": { + "version": "2.3.1", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "5.0.0", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", @@ -3286,6 +3519,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/commander": { "version": "7.2.0", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", @@ -3895,6 +4137,52 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.5.0", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/delaunator": { "version": "5.1.0", "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", @@ -3911,6 +4199,12 @@ "node": ">=6" } }, + "node_modules/destr": { + "version": "2.0.5", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", @@ -3937,6 +4231,18 @@ "@types/trusted-types": "^2.0.7" } }, + "node_modules/dotenv": { + "version": "17.4.2", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", @@ -4401,6 +4707,12 @@ "node": ">=12.0.0" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", @@ -4608,6 +4920,15 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "3.2.0", + "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==", + "dev": true, + "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob": { "version": "8.1.0", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", @@ -4835,6 +5156,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-expression": { "version": "4.0.0", "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", @@ -4858,7 +5194,7 @@ "node_modules/is-extglob": { "version": "2.1.1", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4875,7 +5211,7 @@ "node_modules/is-glob": { "version": "4.0.3", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4884,6 +5220,36 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", @@ -4920,6 +5286,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "3.1.1", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", @@ -4955,6 +5336,18 @@ "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsdom": { "version": "29.0.2", "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", @@ -5614,6 +6007,12 @@ ], "license": "MIT" }, + "node_modules/ohash": { + "version": "2.0.11", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", @@ -5622,6 +6021,26 @@ "wrappy": "1" } }, + "node_modules/open": { + "version": "11.0.0", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", @@ -5719,6 +6138,12 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", @@ -5895,6 +6320,18 @@ "dev": true, "license": "MIT" }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", @@ -6136,6 +6573,16 @@ "node": ">=6" } }, + "node_modules/rc9": { + "version": "3.0.1", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.6", + "destr": "^2.0.5" + } + }, "node_modules/require-directory": { "version": "2.1.1", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", @@ -6241,6 +6688,18 @@ "points-on-path": "^0.2.1" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/runed": { "version": "0.35.1", "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==", @@ -7266,6 +7725,22 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", diff --git a/frontend/package.json b/frontend/package.json index ed13a808..c70eb18e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,12 +10,14 @@ "clean-install": "rm -rf node_modules && npm install", "test": "vitest --run", "licenses:generate": "node licenses-third-party.js generate", - "licenses:check": "node licenses-third-party.js check" + "licenses:check": "node licenses-third-party.js check", + "api:generate": "openapi-ts" }, "devDependencies": { "@eslint/compat": "^2.0.0", "@eslint/js": "^10.0.0", "@faker-js/faker": "^10.0.0", + "@hey-api/openapi-ts": "0.97.1", "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/kit": "^2.5.27", "@sveltejs/vite-plugin-svelte": "^6.0.0", diff --git a/frontend/src/lib/GraphExport.svelte b/frontend/src/lib/GraphExport.svelte index 134f3052..90ff0f68 100644 --- a/frontend/src/lib/GraphExport.svelte +++ b/frontend/src/lib/GraphExport.svelte @@ -273,7 +273,7 @@ bind:value={selectedMediaType} > {#each supportedMediaTypes as mediaType} - + {/each} diff --git a/frontend/src/lib/api/apiDatasetUtils.js b/frontend/src/lib/api/apiDatasetUtils.js index 847ca615..44ade9da 100644 --- a/frontend/src/lib/api/apiDatasetUtils.js +++ b/frontend/src/lib/api/apiDatasetUtils.js @@ -20,11 +20,6 @@ import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); -export async function isReadOnly(datasetName) { - const res = await bec.isReadOnly(datasetName); - return await res.json(); -} - export async function getNamespaces(datasetName) { if (!datasetName) { return []; @@ -32,19 +27,3 @@ export async function getNamespaces(datasetName) { const res = await bec.getNamespaces(datasetName); return await res.json(); } - -export async function getDatasetNames() { - const res = await bec.getDatasetNames(); - let datasetNames = await res.json(); - let readOnlyDatasets = []; - let modifiableDatasets = []; - - for (const dataset of datasetNames) { - if (await isReadOnly(dataset)) { - readOnlyDatasets.push(dataset); - } else { - modifiableDatasets.push(dataset); - } - } - return { modifiable: modifiableDatasets, readonly: readOnlyDatasets }; -} diff --git a/frontend/src/lib/api/backend.js b/frontend/src/lib/api/backend.js index 58ed85bc..f66c856c 100644 --- a/frontend/src/lib/api/backend.js +++ b/frontend/src/lib/api/backend.js @@ -37,15 +37,6 @@ export class BackendConnection { }); } - async getDatasetNames() { - const url = `${PUBLIC_BACKEND_URL}/datasets`; - return fetch(url, { - method: "GET", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - async getGraphNames(datasetName) { const url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs`; return fetch(url, { @@ -56,14 +47,6 @@ export class BackendConnection { }); } - async deleteDataset(datasetName) { - const url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}`; - return fetch(url, { - method: "DELETE", - credentials: "include", - }); - } - async getClassInfo(datasetName, graphURI, classUUID) { const url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/classes/${encodeURIComponent(classUUID)}`; return fetch(url, { @@ -343,15 +326,6 @@ export class BackendConnection { }); } - async isReadOnly(datasetName) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/readonly`; - return await fetch(url, { - method: "GET", - mode: "cors", - credentials: "include", - }); - } - async enableEditing(datasetName) { let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/readonly`; return await fetch(url, { diff --git a/frontend/src/lib/api/hey-api.ts b/frontend/src/lib/api/hey-api.ts new file mode 100644 index 00000000..5f295d60 --- /dev/null +++ b/frontend/src/lib/api/hey-api.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +import { CreateClientConfig } from "./generated/client"; + +export const createClientConfig: CreateClientConfig = (config) => ({ + ...config, + baseUrl: "http://localhost:8080/", + credentials: "include", +}); \ No newline at end of file diff --git a/frontend/src/lib/components/DatasetAndGraphSelection.svelte b/frontend/src/lib/components/DatasetAndGraphSelection.svelte index 3ce5dd22..e9a48942 100644 --- a/frontend/src/lib/components/DatasetAndGraphSelection.svelte +++ b/frontend/src/lib/components/DatasetAndGraphSelection.svelte @@ -18,10 +18,9 @@ import { onMount } from "svelte"; import { v4 as uuidv4 } from "uuid"; - import { isReadOnly } from "$lib/api/apiDatasetUtils.js"; - import { BackendConnection } from "$lib/api/backend.js"; import SelectEditControl from "$lib/components/SelectEditControl.svelte"; - import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; + import { datasetStore } from "$lib/stores/DatasetStore.ts"; + import { graphStore } from "$lib/stores/GraphStore.ts"; let { dataset = $bindable(), @@ -29,15 +28,12 @@ lockedDatasetName, lockedGraphUri, allowSelectionOfReadonlyDatasets = true, - displayAsCard = true, + displayAsCard = true } = $props(); - const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); - const datasetSelectId = `datasetSelect-${uuidv4()}`; const graphSelectId = `graphSelect-${uuidv4()}`; - let datasets = $state([]); let graphNames = $state([]); const datasetLocked = $derived(lockedDatasetName !== undefined); @@ -45,69 +41,46 @@ const graphSelectDisabled = $derived(graphLocked || !dataset); - $effect(() => { + $effect(async () => { if (datasetLocked) return; if (!dataset) { graph = graphLocked ? lockedGraphUri : null; graphNames = []; return; } - loadGraphsFor(dataset); + + await graphStore.load(dataset) + graphNames = graphStore.getGraphs(dataset); + const valid = graphNames.some(graphName => getUri(graphName) === graph); + if (!valid && !graphLocked) { + graph = null; + } }); onMount(async () => { + await datasetStore.load(); if (datasetLocked) dataset = lockedDatasetName; if (graphLocked) graph = lockedGraphUri; - await loadDatasets(); - if (dataset) { - await loadGraphsFor(dataset); - } else { - graphNames = []; - } - }); - - function getUri(graph) { - return (!graph.prefix ? "" : graph.prefix) + graph.suffix; - } - - async function loadDatasets() { - const res = await bec.getDatasetNames(); - const datasetNames = await res.json(); - const newDatasets = datasetNames.map(name => ({ - label: name, - readonly: false, - })); - if (!allowSelectionOfReadonlyDatasets) { - for (const dataset of newDatasets) { - dataset.readonly = await isReadOnly(dataset.label); - } - } - datasets = newDatasets; - if (!datasetLocked && dataset && !allowSelectionOfReadonlyDatasets) { - const selectedDataset = newDatasets.find( - option => option.label === dataset, + const selectedDataset = $datasetStore.data.find( + option => option.label === dataset ); if (!selectedDataset || selectedDataset.readonly) { dataset = null; } } - } - async function loadGraphsFor(dataset) { - if (!dataset) { + if (dataset) { + await graphStore.load(dataset); + graphNames = graphStore.getGraphs(dataset); + } else { graphNames = []; - return; } + }); - const res = await bec.getGraphNames(dataset); - graphNames = await res.json(); - - const valid = graphNames.some(graphName => getUri(graphName) === graph); - if (!valid && !graphLocked) { - graph = null; - } + function getUri(graph) { + return (!graph.prefix ? "" : graph.prefix) + graph.suffix; } @@ -120,13 +93,13 @@ !allowSelectionOfReadonlyDatasets && dataset.readonly} getOptionValue={dataset => dataset.label} getOptionLabel={dataset => dataset.label + (dataset.readonly ? " (readonly)" : "")} - disabled={datasetLocked || datasets.length === 0} + disabled={datasetLocked || ($datasetStore.data?.length ?? 0) === 0} placeholder="Select dataset" onchange={() => (graph = null)} /> diff --git a/frontend/src/lib/components/FileSelectButton.svelte b/frontend/src/lib/components/FileSelectButton.svelte index 42e69972..95d95de6 100644 --- a/frontend/src/lib/components/FileSelectButton.svelte +++ b/frontend/src/lib/components/FileSelectButton.svelte @@ -39,7 +39,7 @@

- {file ? file.name : "no file selected"} + {file ? file.label : "no file selected"}

diff --git a/frontend/src/lib/models/reactive/validity-rules/validityFunctions.js b/frontend/src/lib/models/reactive/validity-rules/validityFunctions.js index 602cd4b4..9f526b84 100644 --- a/frontend/src/lib/models/reactive/validity-rules/validityFunctions.js +++ b/frontend/src/lib/models/reactive/validity-rules/validityFunctions.js @@ -55,7 +55,7 @@ export function isValidDiagramName(diagramName, compareDiagrams) { violations.push("must not be empty"); } - if (compareDiagrams?.some(d => d.name === diagramName)) { + if (compareDiagrams?.some(d => d.label === diagramName)) { violations.push("must be unique"); } @@ -69,12 +69,12 @@ export function isInvalidAssociationLabel(association, associations) { : (associations?.values ?? []); if (violations.length === 0) { if ( - assocList.filter( + assocList.some( a => a.label.value === association?.label?.value && a.namespace.value === association.namespace?.value && a !== association, - ).length > 0 + ) ) { violations.push("must be unique"); } else if ( @@ -101,12 +101,12 @@ export function isInvalidInverseAssociationLabel(association, getClassByUuid) { const assocList = targetClassDto?.associationPairs?.map(pair => pair) ?? []; if (violations.length === 0) { if ( - assocList.filter( + assocList.some( a => a.from.label === association?.inverse?.label?.value && a.from.prefix === association.inverse?.namespace?.value && a.from.uuid !== association.inverse?.uuid?.value, - ).length > 0 + ) ) { violations.push("must be unique"); } else if ( diff --git a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte index 184e74ea..83cde3f7 100644 --- a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte +++ b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte @@ -35,6 +35,7 @@ editorState, forceReloadTrigger, } from "$lib/sharedState.svelte.js"; + import { datasetStore } from "$lib/stores/DatasetStore.ts"; import AssociationEdge from "./components/AssociationEdge.svelte"; import ClassNode from "./components/ClassNode.svelte"; @@ -106,7 +107,7 @@ forceReloadTrigger.subscribe(); editorState.selectedDataset.subscribe(); const dataset = editorState.selectedDataset.getValue(); - isDatasetReadOnly = dataset ? await isReadOnly(dataset) : false; + isDatasetReadOnly = dataset ? await datasetStore.isReadOnly(dataset) : false; }); $effect(() => { @@ -271,11 +272,6 @@ }); } - async function isReadOnly(datasetName) { - const res = await bec.isReadOnly(datasetName); - return await res.json(); - } - function handleNodeClick(nodeClickEvent) { closeContextMenus(); if (nodeClickEvent.node.type === "class") { diff --git a/frontend/src/lib/stores/DatasetStore.ts b/frontend/src/lib/stores/DatasetStore.ts new file mode 100644 index 00000000..f5ed5e5f --- /dev/null +++ b/frontend/src/lib/stores/DatasetStore.ts @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { writable, get, derived } from "svelte/store"; + +import { listDatasets, deleteDataset, isReadOnly as fetchIsReadOnly } from "../api/generated"; + +export type DatasetInfo = { + label: string; + readonly: boolean | null; + prefixes: Set; +}; + +type DatasetsState = { + data: DatasetInfo[] | null; + fetchedAt: number | null; + pending: Promise | null; + error: unknown; +}; + +export const datasetStore = createDatasetStore(); + +function createDatasetStore() { + const store = writable( + { + data: null, + fetchedAt: null, + pending: null, + error: null, + }, + () => { + const state = get(store); + + if (state.data === null && !state.pending) { + void load(); + } + }, + ); + + const { subscribe, update } = store; + + async function load(force = false) { + const state = get(store); + + if (!force && state.data !== null) return; + if (state.pending !== null) return state.pending; + + const promise = (async () => { + try { + const { data, error } = await listDatasets(); + if (error) { + update(s => ({ + ...s, + pending: null, + error, + })); + return; + } + + const datasetNames = data ?? []; + const previous = get(store).data ?? []; + const byName = new Map(previous.map(d => [d.label, d])); + + const baseData: DatasetInfo[] = datasetNames.map(name => { + const prev = byName.get(name); + return { + label: name, + readonly: null, + prefixes: prev?.prefixes ?? new Set(), + }; + }); + + const readonlyPairs = await Promise.all( + baseData.map(async d => { + const { data: roData, error: roError } = + await fetchIsReadOnly({ + path: { datasetName: d.label }, + }); + return { + name: d.label, + readonly: roError ? null : (roData ?? null), + }; + }), + ); + + const readonlyByName = new Map( + readonlyPairs.map(x => [x.name, x.readonly]), + ); + + const nextData: DatasetInfo[] = baseData.map(d => ({ + ...d, + readonly: readonlyByName.get(d.label) ?? null, + })); + + update(s => ({ + ...s, + pending: null, + error: null, + data: nextData, + fetchedAt: Date.now(), + })); + } catch (err) { + update(s => ({ + ...s, + pending: null, + error: err, + })); + } + })(); + + update(s => ({ ...s, pending: promise })); + return promise; + } + + async function remove(datasetName: string) { + const { error } = await deleteDataset({ path: { datasetName } }); + if (error) return { error }; + + update(s => ({ + ...s, + data: s.data?.filter(d => d.label !== datasetName) ?? null, + })); + return { error: null }; + } + + function invalidate() { + void load(true); + } + + async function isReadOnly(datasetName: string, force = false) { + if (force) { + const { data, error } = await fetchIsReadOnly({ + path: { datasetName }, + }); + if (error) { + return; + } + const readonly = data ?? null; + update(s => ({ + ...s, + data: + s.data?.map(d => + d.label === datasetName ? { ...d, readonly: readonly } : d, + ) ?? null, + })); + } + + return derived(datasetStore, $store => { + return $store.data?.find(d => d.label === datasetName)?.readonly ?? null; + }); + } + + function updateReadonly(datasetName: string, readonly: boolean) { + update(s => ({ + ...s, + data: + s.data?.map(d => + d.label === datasetName ? { ...d, readonly } : d, + ) ?? null, + })); + } + + return { + subscribe, + load, + remove, + isReadOnly, + updateReadonly, + invalidate, + }; +} diff --git a/frontend/src/lib/stores/GraphStore.ts b/frontend/src/lib/stores/GraphStore.ts new file mode 100644 index 00000000..18eca186 --- /dev/null +++ b/frontend/src/lib/stores/GraphStore.ts @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { writable, get } from "svelte/store"; + +import { listGraphs, deleteGraph, Uri } from "../api/generated"; + +type DatasetGraphsState = { + data: Uri[] | null; + fetchedAt: number | null; + pending: Promise | null; + error: unknown; +}; + +type GraphsState = { + graphs: Map; +}; + +export const graphStore = createGraphsStore(); + +function createEmptyDatasetState(): DatasetGraphsState { + return { + data: null, + fetchedAt: null, + pending: null, + error: null, + }; +} + +function createGraphsStore() { + const store = writable({ + graphs: new Map(), + }); + + const { subscribe, update } = store; + + function getDatasetState(state: GraphsState, datasetName: string): DatasetGraphsState { + return state.graphs.get(datasetName) ?? createEmptyDatasetState(); + } + + function setDatasetState( + state: GraphsState, + datasetName: string, + next: DatasetGraphsState, + ): GraphsState { + const byDataset = new Map(state.graphs); + byDataset.set(datasetName, next); + return { ...state, graphs: byDataset }; + } + + async function load(datasetName: string, force = false) { + if (!datasetName) return; + + const state = get(store); + const dsState = getDatasetState(state, datasetName); + + if (!force && dsState.data !== null) return; + if (dsState.pending !== null) return dsState.pending; + + const promise = (async () => { + try { + const { data, error } = await listGraphs({ + path: { datasetName }, + }); + + if (error) { + update((s) => + setDatasetState(s, datasetName, { + ...getDatasetState(s, datasetName), + pending: null, + error, + }), + ); + return; + } + + update((s) => + setDatasetState(s, datasetName, { + data: data ?? [], + fetchedAt: Date.now(), + pending: null, + error: null, + }), + ); + } catch (err) { + update((s) => + setDatasetState(s, datasetName, { + ...getDatasetState(s, datasetName), + pending: null, + error: err, + }), + ); + } + })(); + + update((s) => + setDatasetState(s, datasetName, { + ...getDatasetState(s, datasetName), + pending: promise, + }), + ); + + return promise; + } + + function getGraphs(datasetName: string): Uri[] | null { + return getDatasetState(get(store), datasetName).data; + } + + function getStateForDataset(datasetName: string): DatasetGraphsState { + return getDatasetState(get(store), datasetName); + } + + async function remove(datasetName: string, graphURI: string) { + const { error } = await deleteGraph({ + path: { datasetName, graphURI }, + }); + + if (error) return { error }; + + update((s) => { + const dsState = getDatasetState(s, datasetName); + const nextData = + dsState.data?.filter((g) => { + const uri = `${g.prefix ?? ""}${g.suffix}`; + return uri !== graphURI; + }) ?? null; + + return setDatasetState(s, datasetName, { + ...dsState, + data: nextData, + }); + }); + + return { error: null }; + } + + function invalidateDataset(datasetName: string) { + update((s) => { + const byDataset = new Map(s.graphs); + byDataset.delete(datasetName); + return { ...s, byDataset }; + }); + } + + function invalidateAll() { + update(() => ({ graphs: new Map() })); + } + + return { + subscribe, + load, + getGraphs, + getStateForDataset, + remove, + invalidateDataset, + invalidateAll, + }; +} \ No newline at end of file diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 2dc712f1..32b8153a 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -54,6 +54,7 @@ import { goto } from "$app/navigation"; import { page } from "$app/state"; + import { datasetStore } from "$lib/stores/DatasetStore.ts"; /** @type {{children?: import("svelte").Snippet}} */ let { children } = $props(); @@ -91,7 +92,7 @@ forceReloadTrigger.subscribe(); await fetchUndoRedo(); isDatasetReadOnly = selectedDataset - ? await isReadOnly(selectedDataset) + ? await datasetStore.isReadOnly(selectedDataset) : false; }); @@ -138,11 +139,6 @@ canRedo = await fetchCanRedo(); } - async function isReadOnly(datasetName) { - const res = await bec.isReadOnly(datasetName); - return await res.json(); - } - async function reload() { await fetchUndoRedo(); editorState.selectedDataset.trigger(); diff --git a/frontend/src/routes/DatasetDeleteDialog.svelte b/frontend/src/routes/DatasetDeleteDialog.svelte index 0c85e40e..4582fc93 100644 --- a/frontend/src/routes/DatasetDeleteDialog.svelte +++ b/frontend/src/routes/DatasetDeleteDialog.svelte @@ -26,6 +26,7 @@ forceReloadTrigger, editorState, } from "$lib/sharedState.svelte.js"; + import { datasetStore } from "$lib/stores/DatasetStore.ts"; let { showDialog = $bindable(), datasetName } = $props(); @@ -51,8 +52,8 @@ async function deleteDataset() { try { if (datasetName) { - const res = await bec.deleteDataset(datasetName); - if (res && res.ok === false) { + const res = await datasetStore.remove(datasetName); + if (res.error) { toastStore.error( "Delete failed", `Could not delete dataset "${datasetName}".`, diff --git a/frontend/src/routes/ImportDialog.svelte b/frontend/src/routes/ImportDialog.svelte index 0fd07614..a9b308d9 100644 --- a/frontend/src/routes/ImportDialog.svelte +++ b/frontend/src/routes/ImportDialog.svelte @@ -20,11 +20,11 @@ import { Fa } from "svelte-fa"; import { v4 as uuidv4 } from "uuid"; - import { getDatasetNames } from "$lib/api/apiDatasetUtils.js"; import ButtonControl from "$lib/components/ButtonControl.svelte"; import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import ActionDialog from "$lib/dialog/ActionDialog.svelte"; import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; + import { datasetStore } from "$lib/stores/DatasetStore.ts"; import { supportedRDFMediaTypes } from "$lib/utils/fileUtils"; import { @@ -65,9 +65,14 @@ datasetNameUserInput = lockedDatasetName ?? editorState.selectedDataset.getValue(); - const datasetNames = await getDatasetNames(); - modifiableDatasets = datasetNames.modifiable; - readOnlyDatasets = datasetNames.readonly; + await datasetStore.load(); + for (const dataset of $datasetStore.data) { + if (dataset.readonly) { + readOnlyDatasets.push(dataset); + } else { + modifiableDatasets.push(dataset); + } + } } function onClose() { @@ -188,7 +193,7 @@ ? "" : ensureGraphNamespaceUri( fileEntry.graphUri, - fileEntry.file.name, + fileEntry.file.label, ), ); }); @@ -265,6 +270,7 @@ try { const res = await putFiles(filesLocal, datasetNameUserInputLocal); await parseResponse(res, datasetNameUserInputLocal); + await datasetStore.load(true); } catch (e) { console.log("failed to insert data:"); console.log(e); @@ -398,7 +404,7 @@ class="border-border bg-window-background focus:border-orange ring-none w-full rounded border-2 p-2 text-sm outline-none" type="text" value={fileEntry.isZip - ? fileEntry.file.name + ? fileEntry.file.label : fileEntry.graphUri} disabled={fileEntry.isZip} oninput={event => diff --git a/frontend/src/routes/NamespacesDialog.svelte b/frontend/src/routes/NamespacesDialog.svelte index 0436f356..b2c75ff7 100644 --- a/frontend/src/routes/NamespacesDialog.svelte +++ b/frontend/src/routes/NamespacesDialog.svelte @@ -38,6 +38,7 @@ editorState, forceReloadTrigger, } from "$lib/sharedState.svelte.js"; + import { datasetStore } from "$lib/stores/DatasetStore.ts"; let { showDialog = $bindable() } = $props(); @@ -52,7 +53,7 @@ async function onOpen() { datasetName = editorState.selectedDataset.getValue(); if (!datasetName) return; - readonly = await isReadOnly(datasetName); + readonly = await datasetStore.isReadOnly(datasetName); await loadNamespaces(datasetName); } @@ -72,11 +73,6 @@ ); } - async function isReadOnly(datasetNameLocal) { - const res = await bec.isReadOnly(datasetNameLocal); - return await res.json(); - } - async function saveNamespaces() { namespaces?.save(); const plainReactiveNamespaces = namespaces.getPlainObject(); diff --git a/frontend/src/routes/NewGraphDialog.svelte b/frontend/src/routes/NewGraphDialog.svelte index f5ac012b..45b428d5 100644 --- a/frontend/src/routes/NewGraphDialog.svelte +++ b/frontend/src/routes/NewGraphDialog.svelte @@ -18,11 +18,11 @@ diff --git a/frontend/src/routes/ImportDialog.svelte b/frontend/src/routes/ImportDialog.svelte index 5c840540..3dd5463a 100644 --- a/frontend/src/routes/ImportDialog.svelte +++ b/frontend/src/routes/ImportDialog.svelte @@ -21,10 +21,9 @@ import { v4 as uuidv4 } from "uuid"; import ButtonControl from "$lib/components/ButtonControl.svelte"; - import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import ActionDialog from "$lib/dialog/ActionDialog.svelte"; - import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; import { datasetStore } from "$lib/stores/DatasetStore.ts"; + import { graphStore } from "$lib/stores/GraphStore.ts"; import { supportedRDFMediaTypes } from "$lib/utils/fileUtils"; import { @@ -78,6 +77,7 @@ function onClose() { clearInputs(); } + function clearInputs() { datasetNameUserInput = ""; files = []; @@ -200,85 +200,22 @@ return formData; } - function putFiles(files, datasetname) { - return fetch( - `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetname)}/graphs/content`, - { - method: "PUT", - body: buildRequestBody(files), - credentials: "include", - }, - ); - } - async function parseResponse(response, datasetName) { - if (!response.ok) { - console.log("failed to insert data"); - toastStore.error( - "Import failed", - `Could not import into "${datasetName}".`, - ); - return; - } - - const body = await response.json(); - console.log(body.message); - - const failedImports = body.failedImports ?? []; - if (failedImports.length > 0) { - console.warn("failed imports:", failedImports); - } - - //only update the selected dataset and graph if at least one import was successful, otherwise keep the old selection - const importedGraphUris = body.importedGraphUris ?? []; - if (importedGraphUris.length === 0) { - toastStore.error( - "Import failed", - failedImports.length > 0 - ? `${failedImports.length} file(s) could not be imported.` - : "No graphs were imported.", - ); - return; - } - console.log("imported graphs:", importedGraphUris); - - editorState.selectedDataset.updateValue(datasetName); - editorState.selectedGraph.updateValue(importedGraphUris[0]); - editorState.selectedDiagram.updateValue({ type: null, id: null }); - editorState.selectedClassDataset.updateValue(null); - editorState.selectedClassGraph.updateValue(null); - editorState.selectedClassUUID.updateValue(null); - - const importedCount = importedGraphUris.length; - const summary = `${importedCount} graph${importedCount === 1 ? "" : "s"} imported into "${datasetName}".`; - if (failedImports.length > 0) { - toastStore.warning( - "Import partially succeeded", - `${summary} ${failedImports.length} file(s) were skipped.`, - ); - } else { - toastStore.success("Import complete", summary); - } - } - async function importGraphs() { const datasetNameUserInputLocal = getUserInputDatasetName(); - const filesLocal = files; - console.warn( - "Importing files into dataset:", + const { data } = await graphStore.importGraphs( datasetNameUserInputLocal, + buildRequestBody(files), ); - try { - const res = await putFiles(filesLocal, datasetNameUserInputLocal); - await parseResponse(res, datasetNameUserInputLocal); - await datasetStore.load(true); - } catch (e) { - console.log("failed to insert data:"); - console.log(e); - toastStore.error( - "Import failed", - "An unexpected error occurred while importing.", + + if (data.importedGraphUris?.length > 0) { + editorState.selectedDataset.updateValue(datasetNameUserInputLocal); + editorState.selectedGraph.updateValue( + data.importedGraphUris[0] || null, ); - } finally { + editorState.selectedDiagram.updateValue({ type: null, id: null }); + editorState.selectedClassDataset.updateValue(null); + editorState.selectedClassGraph.updateValue(null); + editorState.selectedClassUUID.updateValue(null); forceReloadTrigger.trigger(); } } diff --git a/frontend/src/routes/NamespacesDialog.svelte b/frontend/src/routes/NamespacesDialog.svelte index 48438d2a..6f9221d0 100644 --- a/frontend/src/routes/NamespacesDialog.svelte +++ b/frontend/src/routes/NamespacesDialog.svelte @@ -24,7 +24,6 @@ import TextEditControl from "$lib/components/TextEditControl.svelte"; import ViolationMessages from "$lib/components/ViolationMessages.svelte"; import ModifyDataDialog from "$lib/dialog/ModifyDataDialog.svelte"; - import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; import { mapNamespaceDtoToReactiveNamespace } from "$lib/models/reactive/mapper/map-dto-to-reactive-object.js"; import { mapReactiveNamespaceToNamespaceDto } from "$lib/models/reactive/mapper/map-reactive-object-to-dto.js"; import { ReactiveNamespace } from "$lib/models/reactive/models/reactive-namespace.svelte.js"; @@ -39,7 +38,6 @@ let { showDialog = $bindable() } = $props(); - let datasetName = $state(""); let readonly = $state(false); let namespaces = $state([]); @@ -76,26 +74,12 @@ const namespaceDTOs = plainReactiveNamespaces.map(namespace => { return mapReactiveNamespaceToNamespaceDto(namespace); }); - try { - const res = await datasetStore.saveNamespaces(datasetName, namespaceDTOs); - if (res && res.error) { - toastStore.error( - "Save failed", - `Could not save namespaces for "${datasetName}".`, - ); - return; - } - toastStore.success( - "Namespaces saved", - `Updated for "${datasetName}".`, - ); - } catch (err) { - console.error("Failed to save namespaces:", err); - toastStore.error( - "Save failed", - "An unexpected error occurred while saving namespaces.", - ); - } finally { + const { error } = await datasetStore.saveNamespaces( + datasetName, + namespaceDTOs, + ); + + if (!error) { forceReloadTrigger.trigger(); } } diff --git a/frontend/src/routes/NewClassDialog.svelte b/frontend/src/routes/NewClassDialog.svelte index d71748be..ba9491c5 100644 --- a/frontend/src/routes/NewClassDialog.svelte +++ b/frontend/src/routes/NewClassDialog.svelte @@ -23,11 +23,10 @@ import SelectEditControl from "$lib/components/SelectEditControl.svelte"; import TextEditControl from "$lib/components/TextEditControl.svelte"; import ViolationMessages from "$lib/components/ViolationMessages.svelte"; - import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import ActionDialog from "$lib/dialog/ActionDialog.svelte"; - import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; import { ReactiveValueWrapper } from "$lib/models/reactive/reactive-wrappers/reactive-value-wrapper.svelte.js"; import { isInvalidClassLabel } from "$lib/models/reactive/validity-rules/validityFunctions.js"; + import { classStore } from "$lib/stores/ClassStore.ts"; import { datasetStore } from "$lib/stores/DatasetStore.ts"; import { packageStore } from "$lib/stores/PackageStore.ts"; import { getPackageDisplayLabel } from "$lib/utils/package-label.js"; @@ -39,7 +38,6 @@ } from "../lib/sharedState.svelte.js"; import { getClasses } from "./mainpage/classEditor/fetch-class-editor-context.js"; - let { showDialog = $bindable(), lockedDatasetName, @@ -195,66 +193,35 @@ requestBody.classLayoutPosition = classLayoutPosition; } - try { - const res = await fetch( - PUBLIC_BACKEND_URL + - "/datasets/" + - encodeURIComponent(datasetNameLocal) + - "/graphs/" + - encodeURIComponent(graphURILocal) + - "/classes", - { - method: "POST", - headers: new Headers({ - "Content-Type": "application/json", - }), - body: JSON.stringify(requestBody), - credentials: "include", - }, - ); - if (res.ok) { - const uuid = await res.text(); - console.log("successfully added class"); - onClassCreated({ - classUUID: uuid, - datasetName: datasetNameLocal, - graphURI: graphURILocal, - packageUUID: selectedPackageUUID, - className: classNameLocal.value, - }); - editorState.selectedDataset.updateValue(datasetNameLocal); - editorState.selectedGraph.updateValue(graphURILocal); - editorState.selectedDiagram.updateValue({ - type: DiagramType.PACKAGE, - id: selectedPackageUUID, - }); - editorState.selectedClassDataset.updateValue(datasetNameLocal); - editorState.selectedClassGraph.updateValue(graphURILocal); - editorState.selectedClassUUID.updateValue(uuid); - toastStore.success( - "Class created", - `"${classNameLocal.value}" was added.`, - ); - } else { - console.log("failed to insert data"); - toastStore.error( - "Create failed", - `Could not create class "${classNameLocal.value}".`, - ); - } - } catch (e) { - console.log("failed to add class:", e); - toastStore.error( - "Create failed", - "An unexpected error occurred while creating the class.", - ); - } finally { - forceReloadTrigger.trigger(); - editorState.selectedDataset.trigger(); - editorState.selectedGraph.trigger(); - editorState.selectedDiagram.trigger(); - editorState.selectedClassUUID.trigger(); - } + const { data, error } = await classStore.addClass( + datasetName, + graphURILocal, + requestBody, + ); + if (error) return; + + onClassCreated({ + classUUID: data, + datasetName: datasetNameLocal, + graphURI: graphURILocal, + packageUUID: selectedPackageUUID, + className: classNameLocal.value, + }); + editorState.selectedDataset.updateValue(datasetNameLocal); + editorState.selectedGraph.updateValue(graphURILocal); + editorState.selectedDiagram.updateValue({ + type: DiagramType.PACKAGE, + id: selectedPackageUUID, + }); + editorState.selectedClassDataset.updateValue(datasetNameLocal); + editorState.selectedClassGraph.updateValue(graphURILocal); + editorState.selectedClassUUID.updateValue(data); + + forceReloadTrigger.trigger(); + editorState.selectedDataset.trigger(); + editorState.selectedGraph.trigger(); + editorState.selectedDiagram.trigger(); + editorState.selectedClassUUID.trigger(); } diff --git a/frontend/src/routes/NewGraphDialog.svelte b/frontend/src/routes/NewGraphDialog.svelte index aaf1289c..cbf9f805 100644 --- a/frontend/src/routes/NewGraphDialog.svelte +++ b/frontend/src/routes/NewGraphDialog.svelte @@ -18,11 +18,9 @@ diff --git a/frontend/src/routes/NewPackageDialog.svelte b/frontend/src/routes/NewPackageDialog.svelte index 30180bfd..af325b11 100644 --- a/frontend/src/routes/NewPackageDialog.svelte +++ b/frontend/src/routes/NewPackageDialog.svelte @@ -16,17 +16,15 @@ --> diff --git a/frontend/src/routes/SnapshotDialog.svelte b/frontend/src/routes/SnapshotDialog.svelte index 58268495..b2719638 100644 --- a/frontend/src/routes/SnapshotDialog.svelte +++ b/frontend/src/routes/SnapshotDialog.svelte @@ -42,8 +42,8 @@ const datasetSelectionLocked = $derived(!!lockedDatasetName); function onOpen() { - datasetName = lockedDatasetName ?? editorState.selectedDataset.getValue(); - + datasetName = + lockedDatasetName ?? editorState.selectedDataset.getValue(); } async function snapshotDataset() { diff --git a/frontend/src/routes/changelog/Changes.svelte b/frontend/src/routes/changelog/Changes.svelte index 26cbf811..b0a0c340 100644 --- a/frontend/src/routes/changelog/Changes.svelte +++ b/frontend/src/routes/changelog/Changes.svelte @@ -39,7 +39,8 @@ $effect(async () => { forceReloadTrigger.subscribe(); if (selectedDatasetName) { - readonlyDataset = await datasetStore.isReadOnly(selectedDatasetName); + readonlyDataset = + await datasetStore.isReadOnly(selectedDatasetName); } }); diff --git a/frontend/src/routes/changelog/Navigation.svelte b/frontend/src/routes/changelog/Navigation.svelte index dcd2d334..8f95fb18 100644 --- a/frontend/src/routes/changelog/Navigation.svelte +++ b/frontend/src/routes/changelog/Navigation.svelte @@ -26,7 +26,7 @@ editorState, } from "$lib/sharedState.svelte.js"; import { datasetStore } from "$lib/stores/DatasetStore.ts"; - import { graphURIStore } from "$lib/stores/GraphURIStore.ts"; + import { graphStore } from "$lib/stores/GraphStore.ts"; let datasetList = $state([]); let selectedDatasetName = $derived(editorState.selectedDataset.getValue()); @@ -50,10 +50,12 @@ graphs: [], showContents: showDatasetContents, }); - await graphURIStore.load(datasetName); - graphURIStore.getGraphNames(datasetName).forEach(graphUri => - newDatasetList.at(-1).graphs.push(graphUri), - ); + await graphStore.load(datasetName); + graphStore + .getGraphNames(datasetName) + .forEach(graphUri => + newDatasetList.at(-1).graphs.push(graphUri), + ); } datasetList = newDatasetList; } diff --git a/frontend/src/routes/layout/menu-bar/Edit.svelte b/frontend/src/routes/layout/menu-bar/Edit.svelte index cdc7a242..bf8b8f31 100644 --- a/frontend/src/routes/layout/menu-bar/Edit.svelte +++ b/frontend/src/routes/layout/menu-bar/Edit.svelte @@ -17,36 +17,33 @@ diff --git a/frontend/src/routes/mainpage/classEditor/components/associations/associationEditorDialog/AssociationEditorDialog.svelte b/frontend/src/routes/mainpage/classEditor/components/associations/associationEditorDialog/AssociationEditorDialog.svelte index 87f20972..87c9cd31 100644 --- a/frontend/src/routes/mainpage/classEditor/components/associations/associationEditorDialog/AssociationEditorDialog.svelte +++ b/frontend/src/routes/mainpage/classEditor/components/associations/associationEditorDialog/AssociationEditorDialog.svelte @@ -22,6 +22,7 @@ import { mapReactiveAssociationToAssociationDto } from "$lib/models/reactive/mapper/map-reactive-object-to-dto.js"; import { ReactiveAssociation } from "$lib/models/reactive/models/reactive-association.svelte.js"; import { forceReloadTrigger } from "$lib/sharedState.svelte.js"; + import { classStore } from "$lib/stores/ClassStore.ts"; import Direct from "./Direct.svelte"; import { saveApiAssociationToBackend } from "../save-association-to-backend.js"; @@ -37,8 +38,6 @@ let isNewAssociation = $state(true); let readonly = $derived(classEditorContext?.readonly); - let bec = $derived(classEditorContext?.backendConnection); - $effect(() => { (async () => { const targetValue = association?.target?.value; @@ -48,17 +47,13 @@ const existingClassInfo = ctx.getTargetClassInfoByUuid(targetValue); if (!existingClassInfo) { - const res = await bec.getClassInfo( + const res = await classStore.getClassInfo( ctx.datasetName, ctx.graphUri, targetValue, ); - if (res && res.ok) { - const text = await res.text(); - if (text) { - const classInfo = await res.json(); - ctx.addTargetClassInfo(classInfo); - } + if (res) { + ctx.addTargetClassInfo(res); } } diff --git a/frontend/src/routes/mainpage/classEditor/components/associations/save-association-to-backend.js b/frontend/src/routes/mainpage/classEditor/components/associations/save-association-to-backend.js index 47fe9c74..592bc853 100644 --- a/frontend/src/routes/mainpage/classEditor/components/associations/save-association-to-backend.js +++ b/frontend/src/routes/mainpage/classEditor/components/associations/save-association-to-backend.js @@ -15,40 +15,46 @@ * */ -import { BackendConnection } from "$lib/api/backend.js"; -import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { editorState } from "$lib/sharedState.svelte.js"; -const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); +import { classStore } from "$lib/stores/ClassStore.ts"; -export function saveApiAssociationToBackend( +export async function saveApiAssociationToBackend( dataset, graph, classUUID, associationDTO, isNewAssociation, ) { - const saveAssociationPairCall = isNewAssociation - ? bec.postAssociationPair(dataset, graph, classUUID, associationDTO) - : bec.putAssociationPair(dataset, graph, classUUID, associationDTO); + const res = isNewAssociation + ? await classStore.addAssociationPair( + dataset, + graph, + classUUID, + associationDTO, + ) + : await classStore.replaceAssociationPair( + dataset, + graph, + classUUID, + associationDTO, + ); - return saveAssociationPairCall - .then(async res => { - if (res.ok) { - const associationUUIDs = await res.json(); - console.log( - "Successfully saved association:", - associationUUIDs.fromUUID, - associationUUIDs.toUUID, - ); - return { ok: true, associationUUIDs }; - } + try { + if (!res.error) { + const associationUUIDs = res.data; + console.log( + "Successfully saved association:", + associationUUIDs.fromUUID, + associationUUIDs.toUUID, + ); + return { ok: true, associationUUIDs }; + } - const errorText = await res.text(); - console.error("Could not save association:", errorText); - return { ok: false, errorText }; - }) - .finally(() => { - editorState.selectedClassUUID.trigger(); - editorState.selectedDiagram.trigger(); - }); + const errorText = await res.error; + console.error("Could not save association:", errorText); + return { ok: false, errorText }; + } finally { + editorState.selectedClassUUID.trigger(); + editorState.selectedDiagram.trigger(); + } } diff --git a/frontend/src/routes/mainpage/classEditor/components/attributes/save-attribute-to-backend.js b/frontend/src/routes/mainpage/classEditor/components/attributes/save-attribute-to-backend.js index 9cff6ca3..ea9c5f4d 100644 --- a/frontend/src/routes/mainpage/classEditor/components/attributes/save-attribute-to-backend.js +++ b/frontend/src/routes/mainpage/classEditor/components/attributes/save-attribute-to-backend.js @@ -15,11 +15,8 @@ * */ -import { BackendConnection } from "$lib/api/backend.js"; -import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { editorState } from "$lib/sharedState.svelte.js"; - -const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); +import { classStore } from "$lib/stores/ClassStore.ts"; export async function saveApiAttributeToBackend( dataset, @@ -28,21 +25,24 @@ export async function saveApiAttributeToBackend( attribute, isNewAttribute, ) { - const saveAttributeCall = isNewAttribute - ? bec.postAttribute(dataset, graph, classUUID, attribute) - : bec.putAttribute(dataset, graph, classUUID, attribute); + const res = isNewAttribute + ? await classStore.addAttribute(dataset, graph, classUUID, attribute) + : await classStore.replaceAttribute( + dataset, + graph, + classUUID, + attribute, + ); try { - const res = await saveAttributeCall; - if (res.ok) { - const attributeUUID = await res.json(); + if (!res.error) { + const attributeUUID = res.data; console.log("Successfully saved attribute:", attributeUUID); return { ok: true, attributeUUID }; } - const errorText = await res.text(); - console.error("Could not save attribute:", errorText); - return { ok: false, errorText }; + console.error("Could not save attribute:", res.error); + return { ok: false, error: res.error }; } finally { editorState.selectedClassUUID.trigger(); editorState.selectedDiagram.trigger(); diff --git a/frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js b/frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js index efc641f7..cf68e8c5 100644 --- a/frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js +++ b/frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js @@ -15,11 +15,8 @@ * */ -import { BackendConnection } from "$lib/api/backend.js"; -import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { editorState } from "$lib/sharedState.svelte.js"; - -const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); +import { classStore } from "$lib/stores/ClassStore.ts"; export async function saveApiEnumEntryToBackend( dataset, @@ -28,19 +25,23 @@ export async function saveApiEnumEntryToBackend( enumEntry, isNewEnumEntry, ) { - const saveEnumEntryCall = isNewEnumEntry - ? bec.postEnumEntry(dataset, graph, classUUID, enumEntry) - : bec.putEnumEntry(dataset, graph, classUUID, enumEntry); + const res = isNewEnumEntry + ? await classStore.addEnumEntry(dataset, graph, classUUID, enumEntry) + : await classStore.replaceEnumEntry( + dataset, + graph, + classUUID, + enumEntry, + ); try { - const res = await saveEnumEntryCall; - if (res.ok) { - const enumEntryUUID = await res.json(); + if (!res.error) { + const enumEntryUUID = res.data; console.log("Successfully saved enum entry:", enumEntryUUID); return { ok: true, enumEntryUUID }; } - const errorText = await res.text(); + const errorText = await res.error; console.error("Could not save enum entry:", errorText); return { ok: false, errorText }; } finally { diff --git a/frontend/src/routes/mainpage/classEditor/fetch-class-editor-context.js b/frontend/src/routes/mainpage/classEditor/fetch-class-editor-context.js index 596ef78a..67d46771 100644 --- a/frontend/src/routes/mainpage/classEditor/fetch-class-editor-context.js +++ b/frontend/src/routes/mainpage/classEditor/fetch-class-editor-context.js @@ -18,6 +18,7 @@ import { BackendConnection } from "$lib/api/backend.js"; import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { Class, DataType, DataTypeTypes, Package } from "$lib/models/dto"; +import { classStore } from "$lib/stores/ClassStore.ts"; import { packageStore } from "$lib/stores/PackageStore.ts"; const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); @@ -97,9 +98,9 @@ export async function getDataTypes(datasetName, graphUri) { } export async function getClasses(datasetName, graphUri) { - const res = await bec.getClasses(datasetName, graphUri, true); - let classesDto = await res.json(); - let classes = classesDto.map(cls => new Class(cls)); + await classStore.load(datasetName, graphUri, true); + const classDTOs = await classStore.getClasses(datasetName, graphUri, true); + let classes = classDTOs.map(cls => new Class(cls)); console.log("CLASSES:", classes); return classes; } diff --git a/frontend/src/routes/mainpage/packageEditorDialog.svelte b/frontend/src/routes/mainpage/packageEditorDialog.svelte index 5b914819..dfd080f4 100644 --- a/frontend/src/routes/mainpage/packageEditorDialog.svelte +++ b/frontend/src/routes/mainpage/packageEditorDialog.svelte @@ -15,29 +15,22 @@ - --> diff --git a/frontend/src/routes/mainpage/packageNavigation/build-nav-object.js b/frontend/src/routes/mainpage/packageNavigation/build-nav-object.js index 5c177a2e..7d94cb27 100644 --- a/frontend/src/routes/mainpage/packageNavigation/build-nav-object.js +++ b/frontend/src/routes/mainpage/packageNavigation/build-nav-object.js @@ -17,13 +17,13 @@ import { get } from "svelte/store"; -import { BackendConnection } from "$lib/api/backend.js"; -import { PUBLIC_BACKEND_URL } from "$lib/config/runtime.js"; import { URI } from "$lib/models/dto/index.ts"; import { NavEntry } from "$lib/models/nav/NavEntry.svelte.js"; import { DiagramType, editorState } from "$lib/sharedState.svelte.js"; +import { classStore } from "$lib/stores/ClassStore.ts"; import { datasetStore } from "$lib/stores/DatasetStore.ts"; -import { graphURIStore } from "$lib/stores/GraphURIStore.ts"; +import { graphStore } from "$lib/stores/GraphStore.ts"; +import { packageStore } from "$lib/stores/PackageStore.ts"; import { getPackageDisplayLabel } from "$lib/utils/package-label.js"; import { @@ -32,9 +32,6 @@ import { isSelectedPackage, isSelectedClass, } from "./packageNavigationUtils.svelte.js"; -import { packageStore } from "$lib/stores/PackageStore.ts"; - -const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); /** * @description Reuses an existing NavEntry by id or creates a new one. Preserves isOpen. @@ -81,7 +78,10 @@ export async function getNavEntryList(existingDatasetNavList) { const freshEntries = datasets .sort((a, b) => a.label.localeCompare(b.label)) .map(dataset => - reuseOrCreate(existingDatasetNavList, { label: dataset.label, id: dataset.label }), + reuseOrCreate(existingDatasetNavList, { + label: dataset.label, + id: dataset.label, + }), ); const result = existingDatasetNavList ?? []; @@ -126,8 +126,8 @@ async function populateDataset(datasetNavEntry) { async function getGraphNames(datasetName) { try { - await graphURIStore.load(datasetName); - return graphURIStore.getGraphURIs(datasetName); + await graphStore.load(datasetName); + return graphStore.getGraphURIs(datasetName); } catch (err) { console.error( "Error fetching graph names for dataset " + datasetName, @@ -140,9 +140,16 @@ async function getGraphNames(datasetName) { export async function populateGraph(datasetNavObject, graphNavObject) { const existingPackageList = graphNavObject.children; await packageStore.load(datasetNavObject.id, graphNavObject.id); - const packageData = packageStore.getPackages(datasetNavObject.id, graphNavObject.id); + const packageData = packageStore.getPackages( + datasetNavObject.id, + graphNavObject.id, + ); - const allClasses = await getClasses(datasetNavObject.id, graphNavObject.id); + await classStore.load(datasetNavObject.id, graphNavObject.id); + const allClasses = await classStore.getClasses( + datasetNavObject.id, + graphNavObject.id, + ); const freshEntries = [ ...packageData.internal.map(pack => @@ -247,19 +254,3 @@ function populatePackage(packageNavObject, allClasses, datasetId, graphId) { return packageNavObject; } - -async function getClasses(datasetName, graphURI) { - try { - const res = await bec.getClasses(datasetName, graphURI); - return await res.json(); - } catch (err) { - console.error( - "Error fetching classes for dataset " + - datasetName + - " and graph " + - graphURI, - err, - ); - return []; - } -} diff --git a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/AddToDatasetDiagramDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/AddToDatasetDiagramDialog.svelte index fcb6c286..8727a01c 100644 --- a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/AddToDatasetDiagramDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/AddToDatasetDiagramDialog.svelte @@ -16,12 +16,10 @@ --> diff --git a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDiagramDeleteDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDiagramDeleteDialog.svelte index 60486ae7..17b0a7f7 100644 --- a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDiagramDeleteDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDiagramDeleteDialog.svelte @@ -18,65 +18,40 @@ diff --git a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomGraphDiagramDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomGraphDiagramDialog.svelte index f1b9ac89..be56c1fe 100644 --- a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomGraphDiagramDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomGraphDiagramDialog.svelte @@ -16,19 +16,17 @@ --> diff --git a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/RemoveFromDiagramDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/RemoveFromDiagramDialog.svelte index 21702213..05edf258 100644 --- a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/RemoveFromDiagramDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/RemoveFromDiagramDialog.svelte @@ -18,11 +18,9 @@ {#if diagrams.length > 0} diff --git a/frontend/src/routes/mainpage/packageNavigation/GraphSection.svelte b/frontend/src/routes/mainpage/packageNavigation/GraphSection.svelte index 874ad165..c13e850d 100644 --- a/frontend/src/routes/mainpage/packageNavigation/GraphSection.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/GraphSection.svelte @@ -36,14 +36,13 @@ } from "@fortawesome/free-solid-svg-icons"; import { getContext } from "svelte"; - import { BackendConnection } from "$lib/api/backend.js"; import { ContextMenu } from "$lib/components/bitsui/contextmenu"; import NavigationEntry from "$lib/components/navigation/NavigationEntry.svelte"; - import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { editorState, forceReloadTrigger, } from "$lib/sharedState.svelte.js"; + import { ontologyStore } from "$lib/stores/OntologyStore.ts"; import { versionControlStore } from "$lib/stores/VersionControlStore.ts"; import { shortenIri } from "$lib/utils/iri.js"; @@ -70,8 +69,6 @@ readonly = false, } = $props(); - const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); - let ontology = $state(); let showExportDialog = $state(false); let showDeleteDialog = $state(false); @@ -108,7 +105,9 @@ }); async function initialize() { - ontology = await getOntology(); + await ontologyStore.loadOntology(datasetNavEntry.id, graphNavEntry.id); + const { data } = await ontologyStore.getOntologyForGraph(datasetNavEntry.id, graphNavEntry.id); + ontology = data; canUndo = versionControlStore.canUndo( datasetNavEntry.id, graphNavEntry.id, @@ -119,18 +118,6 @@ ); } - async function getOntology() { - const res = await bec.getOntology( - datasetNavEntry.label, - graphNavEntry.id, - ); - let content = await res.text(); - if (!content) { - return null; - } - return JSON.parse(content); - } - function focusGraphContext() { const nextDataset = datasetNavEntry.label; const nextGraph = graphNavEntry.id; diff --git a/frontend/src/routes/mainpage/packageNavigation/build-nav-object.js b/frontend/src/routes/mainpage/packageNavigation/build-nav-object.js index 7d94cb27..3e3290e1 100644 --- a/frontend/src/routes/mainpage/packageNavigation/build-nav-object.js +++ b/frontend/src/routes/mainpage/packageNavigation/build-nav-object.js @@ -125,16 +125,8 @@ async function populateDataset(datasetNavEntry) { } async function getGraphNames(datasetName) { - try { - await graphStore.load(datasetName); - return graphStore.getGraphURIs(datasetName); - } catch (err) { - console.error( - "Error fetching graph names for dataset " + datasetName, - err, - ); - return []; - } + await graphStore.load(datasetName); + return graphStore.getGraphs(datasetName) ?? []; } export async function populateGraph(datasetNavObject, graphNavObject) { diff --git a/frontend/src/routes/mainpage/packageNavigation/ontology-editor-dialog/OntologyDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/ontology-editor-dialog/OntologyDialog.svelte index a708bf79..e0ba47af 100644 --- a/frontend/src/routes/mainpage/packageNavigation/ontology-editor-dialog/OntologyDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/ontology-editor-dialog/OntologyDialog.svelte @@ -19,18 +19,16 @@ import { faRotateLeft, faSave } from "@fortawesome/free-solid-svg-icons"; import { Fa } from "svelte-fa"; - import { BackendConnection } from "$lib/api/backend.js"; import { DropdownMenu } from "$lib/components/bitsui/dropdown/index.js"; import ButtonControl from "$lib/components/ButtonControl.svelte"; import LoadingSpinner from "$lib/components/LoadingSpinner.svelte"; import SearchableSelect from "$lib/components/SearchableSelect.svelte"; - import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import ActionDialog from "$lib/dialog/ActionDialog.svelte"; import DiscardCancelConfirmDialog from "$lib/dialog/DiscardCancelConfirmDialog.svelte"; - import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; import { ReactiveOntology } from "$lib/models/reactive/models/ontology/reactive-ontology.svelte.js"; import { forceReloadTrigger } from "$lib/sharedState.svelte.js"; import { datasetStore } from "$lib/stores/DatasetStore.ts"; + import { ontologyStore } from "$lib/stores/OntologyStore.ts"; import AddKnownFieldsDialog from "./AddKnownFieldsDialog.svelte"; import OntologyEntryRow from "./OntologyEntryRow.svelte"; @@ -45,8 +43,6 @@ onSubmit, } = $props(); - const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); - let showAddKnownEntriesPopUp = $state(false); let showDiscardSaveConfirmDialog = $state(false); @@ -135,30 +131,18 @@ if (!graphUri) { return null; } - const res = await bec.getOntology(dataset, graphUri); - let content = await res.text(); - if (!content) { - return null; - } - return JSON.parse(content); + await ontologyStore.loadOntology(dataset, graphUri); + const { data } = await ontologyStore.getOntologyForGraph(dataset, graphUri); + return data; } async function saveOntology(datasetName, graphUri, ontologyObject) { const serializable = ontologyObject.getPlainObject(); - const res = ontologyObject.uuid.value - ? await bec.putOntology(datasetName, graphUri, serializable) - : await bec.postOntology(datasetName, graphUri, serializable); - if (res && res.ok === false) { - toastStore.error( - "Save failed", - "Could not save the profile header.", - ); - return; + if (ontologyObject.uuid.value) { + await ontologyStore.replaceOntology(datasetName, graphUri, serializable); + } else { + await ontologyStore.createOntology(datasetName, graphUri, serializable); } - toastStore.success( - "Profile header saved", - "The profile header was saved.", - ); } function scrollEntriesToBottom() { From 21d9f32b01d9f3e5f4982cbbab8470a686928f80 Mon Sep 17 00:00:00 2001 From: Finn Tarnowsky Date: Thu, 11 Jun 2026 16:08:48 +0200 Subject: [PATCH 6/8] Template syntax in string literal Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Signed-off-by: Finn Tarnowsky --- frontend/src/lib/stores/GraphStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/stores/GraphStore.ts b/frontend/src/lib/stores/GraphStore.ts index be62c06c..0fdf8eac 100644 --- a/frontend/src/lib/stores/GraphStore.ts +++ b/frontend/src/lib/stores/GraphStore.ts @@ -159,7 +159,7 @@ function createGraphStore() { if (error) { console.error( - 'Failed to create empty graph "${graphURI}" to dataset "${datasetName}"`', + `Failed to create empty graph "${graphURI}" to dataset "${datasetName}"`, ); toastStore.error( "Create failed", From cdf8589b16bee13bb6e506fd5d110c82e2c17a20 Mon Sep 17 00:00:00 2001 From: tarn Date: Thu, 18 Jun 2026 16:09:38 +0200 Subject: [PATCH 7/8] use stores in frontend --- ...ntologyKnownInformationRESTController.java | 11 +- ...hemaComparisonFromFilesRESTController.java | 3 +- .../datasets/DatasetRESTController.java | 3 +- .../GlobalClassLayoutDataRESTController.java | 4 +- .../CustomDatasetDiagramsRESTController.java | 2 +- .../graphs/AllPackagesRESTController.java | 79 ++-- .../GraphBulkContentRESTController.java | 1 - ...OntologyGenerateEntriesRESTController.java | 2 +- .../SchemaComparisonRESTController.java | 6 +- .../CustomDiagramsRESTController.java | 2 +- .../MigrationContextRESTController.java | 3 +- .../org/rdfarchitect/api/dto/DatasetDTO.java | 2 + .../services/select/ListDatasetsUseCase.java | 5 +- frontend/src/lib/GraphExport.svelte | 13 +- frontend/src/lib/api/apiGlobalUtils.js | 31 -- frontend/src/lib/api/backend.js | 289 --------------- .../DatasetAndGraphSelection.svelte | 4 +- .../SvelteFlowPaneContextMenu.svelte | 20 +- .../svelteflow/svelteFlowWrapper.svelte | 51 +-- frontend/src/lib/stores/ClassStore.ts | 158 +++----- frontend/src/lib/stores/DatasetStore.ts | 7 +- frontend/src/lib/stores/DatatypesStore.ts | 336 ++++++++++++++++++ frontend/src/lib/stores/GraphStore.ts | 2 +- frontend/src/lib/stores/OntologyStore.ts | 98 ++++- .../src/lib/stores/VersionControlStore.ts | 46 +-- frontend/src/lib/stores/XSDDatatypesStore.ts | 76 ++++ frontend/src/routes/+layout.svelte | 24 +- .../src/routes/DatasetDeleteDialog.svelte | 2 +- frontend/src/routes/ImportDialog.svelte | 7 +- frontend/src/routes/Searchbar.svelte | 25 +- frontend/src/routes/SnapshotDialog.svelte | 18 +- frontend/src/routes/changelog/Changes.svelte | 23 +- .../src/routes/changelog/ChangesRow.svelte | 15 +- .../src/routes/changelog/Navigation.svelte | 5 +- .../src/routes/compare/CompareDialog.svelte | 30 +- .../DeleteDependenciesDialog.svelte | 32 +- .../src/routes/layout/menu-bar/Edit.svelte | 7 +- .../mainpage/classEditor/classEditor.svelte | 12 +- .../components/ClassEditorButtons.svelte | 3 +- .../enum-entries/EnumEntryEditorDialog.svelte | 35 +- .../save-enum-entry-to-backend.js | 51 --- .../classEditor/fetch-class-editor-context.js | 39 +- .../CustomDiagramButton.svelte | 4 +- .../CustomDiagramsSection.svelte | 32 +- .../ExtendClassDialog.svelte | 55 +-- .../packageNavigation/GraphSection.svelte | 5 +- .../CustomDatasetDiagramDialog.svelte | 27 +- .../customDiagramDialogUtils.js | 52 ++- .../AddKnownFieldsDialog.svelte | 16 +- .../OntologyDialog.svelte | 17 +- .../OntologyEntryRow.svelte | 8 +- .../save-copy-class-to-backend.js | 51 +-- .../routes/mainpage/renderingWrapper.svelte | 70 ++-- .../routes/migrate/steps/SelectSchemas.svelte | 11 +- 54 files changed, 973 insertions(+), 957 deletions(-) delete mode 100644 frontend/src/lib/api/apiGlobalUtils.js delete mode 100644 frontend/src/lib/api/backend.js create mode 100644 frontend/src/lib/stores/DatatypesStore.ts create mode 100644 frontend/src/lib/stores/XSDDatatypesStore.ts delete mode 100644 frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/OntologyKnownInformationRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/OntologyKnownInformationRESTController.java index 9788c1b5..352786d9 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/OntologyKnownInformationRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/OntologyKnownInformationRESTController.java @@ -19,6 +19,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -57,10 +58,16 @@ public class OntologyKnownInformationRESTController { content = @Content( mediaType = "application/json", - schema = @Schema(implementation = OntologyField.class))) + array = + @ArraySchema( + schema = + @Schema( + implementation = + OntologyField + .class)))) }) @GetMapping - public List getOntology( + public List getKnownOntologyFields( @Parameter(description = "The name/url of the inquirer.") @RequestHeader( value = HttpHeaders.ORIGIN, diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/SchemaComparisonFromFilesRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/SchemaComparisonFromFilesRESTController.java index af7ca7a8..4056c6f5 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/SchemaComparisonFromFilesRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/SchemaComparisonFromFilesRESTController.java @@ -53,6 +53,7 @@ public class SchemaComparisonFromFilesRESTController { @Operation( summary = "compare schemas", description = "Compare two given graphs", + tags = {"comparison"}, responses = { @ApiResponse( responseCode = "200", @@ -68,7 +69,7 @@ public class SchemaComparisonFromFilesRESTController { .class)))) }) @PostMapping - public List compareSchemas( + public List compareSchemasFromFiles( @Parameter(description = "The name/url of the inquirer.") @RequestHeader( value = HttpHeaders.ORIGIN, diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/DatasetRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/DatasetRESTController.java index 1332e1e6..84058152 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/DatasetRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/DatasetRESTController.java @@ -51,7 +51,8 @@ public class DatasetRESTController { @Operation( summary = "List datasets", - description = "Lists all non-snapshots datasets including their prefixes and read-only status.", + description = + "Lists all non-snapshots datasets including their prefixes and read-only status.", tags = {"dataset"}, responses = {@ApiResponse(responseCode = "200")}) @GetMapping diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/GlobalClassLayoutDataRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/GlobalClassLayoutDataRESTController.java index e109d105..b6994a34 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/GlobalClassLayoutDataRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/GlobalClassLayoutDataRESTController.java @@ -50,12 +50,12 @@ public class GlobalClassLayoutDataRESTController { private final UpdateClassPositionsUseCase updateClassPositionsUseCase; @Operation( - summary = "updates class positions", + summary = "updates class positions on dataset level", description = "Updates the positions for all the classes provided in the request body with the provided coordinates.", tags = {"diagram", "layout", "class"}) @PutMapping - public String updateClassPositions( + public String updateDatasetClassPositions( @Parameter(description = "The name/url of the inquirer.") @RequestHeader(value = "origin", required = false, defaultValue = "unknown") String originURL, diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CustomDatasetDiagramsRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CustomDatasetDiagramsRESTController.java index e00b5712..c253d556 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CustomDatasetDiagramsRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CustomDatasetDiagramsRESTController.java @@ -59,7 +59,7 @@ public class CustomDatasetDiagramsRESTController { private final ReplaceCustomDiagramUseCase replaceCustomDiagram; @GetMapping - public RenderingDataDTO getDiagramRenderingData( + public RenderingDataDTO getCustomDatasetViewRenderingData( @Parameter(description = "The name/url of the inquirer.") @RequestHeader( value = HttpHeaders.ORIGIN, diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/AllPackagesRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/AllPackagesRESTController.java index e4237ffa..2f92113c 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/AllPackagesRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/AllPackagesRESTController.java @@ -22,9 +22,9 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; -import java.util.List; -import java.util.UUID; + import lombok.RequiredArgsConstructor; + import org.rdfarchitect.api.dto.packages.PackageDTO; import org.rdfarchitect.database.GraphIdentifier; import org.rdfarchitect.services.ExpandURIUseCase; @@ -42,6 +42,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.UUID; + @RestController @RequestMapping("api/datasets/{datasetName}/graphs/{graphURI}/packages") @RequiredArgsConstructor @@ -54,44 +57,40 @@ public class AllPackagesRESTController { private final ListInternalPackagesUseCase listInternalPackagesUseCase; private final ListExternalPackagesUseCase listExternalPackagesUseCase; - /** - * Record for response to frontend, contains two lists for internal and external packages. - */ + /** Record for response to frontend, contains two lists for internal and external packages. */ public record ListPackagesResponse( - List internalPackageList, List externalPackageList) { - } + List internalPackageList, List externalPackageList) {} @Operation( summary = "list packages", description = "Get two lists of packages: internal and external packages.", tags = {"graph"}, responses = { - @ApiResponse( - responseCode = "200", - content = - @Content( - mediaType = "application/json", - schema = - @Schema( - implementation = - ListPackagesResponse - .class))) + @ApiResponse( + responseCode = "200", + content = + @Content( + mediaType = "application/json", + schema = + @Schema( + implementation = + ListPackagesResponse.class))) }) @GetMapping public ListPackagesResponse listPackages( @Parameter(description = "The name/url of the inquirer.") - @RequestHeader( - value = HttpHeaders.ORIGIN, - required = false, - defaultValue = "unknown") - String originURL, + @RequestHeader( + value = HttpHeaders.ORIGIN, + required = false, + defaultValue = "unknown") + String originURL, @Parameter(description = "The literal name of the dataset.") @PathVariable - String datasetName, + String datasetName, @Parameter( - description = - "The url encoded uri of the graph, or \"default\" to access the default graph.") - @PathVariable - String graphURI) { + description = + "The url encoded uri of the graph, or \"default\" to access the default graph.") + @PathVariable + String graphURI) { logger.info( "Received GET request: \"/api/datasets/{{}}/graphs/{{}}/packages\" from \"{}\".", datasetName, @@ -124,22 +123,22 @@ public ListPackagesResponse listPackages( @PostMapping public UUID addPackage( @Parameter(description = "The name/url of the inquirer.") - @RequestHeader( - value = HttpHeaders.ORIGIN, - required = false, - defaultValue = "unknown") - String originURL, + @RequestHeader( + value = HttpHeaders.ORIGIN, + required = false, + defaultValue = "unknown") + String originURL, @Parameter(description = "The literal name of the dataset.") @PathVariable - String datasetName, + String datasetName, @Parameter( - description = - "The url encoded uri of the graph, or \"default\" to access the default graph.") - @PathVariable - String graphURI, + description = + "The url encoded uri of the graph, or \"default\" to access the default graph.") + @PathVariable + String graphURI, @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "DTO for the package to be created") - @RequestBody - PackageDTO packageDTO) { + description = "DTO for the package to be created") + @RequestBody + PackageDTO packageDTO) { logger.info( "Received POST request: \"/api/datasets/{{}}/graphs/{{}}/packages\" from \"{}\".", diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/GraphBulkContentRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/GraphBulkContentRESTController.java index 816a4ac9..40fed42a 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/GraphBulkContentRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/GraphBulkContentRESTController.java @@ -34,7 +34,6 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/OntologyGenerateEntriesRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/OntologyGenerateEntriesRESTController.java index 4911151a..0d700b5b 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/OntologyGenerateEntriesRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/OntologyGenerateEntriesRESTController.java @@ -71,7 +71,7 @@ public class OntologyGenerateEntriesRESTController { .class)))) }) @GetMapping - public List getOntology( + public List getOntologyEntries( @Parameter(description = "The name/url of the inquirer.") @RequestHeader( value = HttpHeaders.ORIGIN, diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/SchemaComparisonRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/SchemaComparisonRESTController.java index 2a038fe0..1a10833e 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/SchemaComparisonRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/SchemaComparisonRESTController.java @@ -58,7 +58,7 @@ public class SchemaComparisonRESTController { @Operation( summary = "compare schemas", description = "Compare a given graph with the specified graph from the dataset", - tags = {"graph"}, + tags = {"comparison"}, responses = { @ApiResponse( responseCode = "200", @@ -113,7 +113,7 @@ public List compareSchemas( @Operation( summary = "compare schemas", description = "Compare two graphs stored in the database", - tags = {"graph"}, + tags = {"comparison"}, responses = { @ApiResponse( responseCode = "200", @@ -129,7 +129,7 @@ public List compareSchemas( .class)))) }) @GetMapping() - public List compareDatasetSchemas( + public List compareStoredSchemas( @Parameter(description = "The name/url of the inquirer.") @RequestHeader( value = HttpHeaders.ORIGIN, diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/diagrams/CustomDiagramsRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/diagrams/CustomDiagramsRESTController.java index 889c8a0e..a5abc965 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/diagrams/CustomDiagramsRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/diagrams/CustomDiagramsRESTController.java @@ -63,7 +63,7 @@ public class CustomDiagramsRESTController { private final ExpandURIUseCase expandURIUseCase; @GetMapping - public RenderingDataDTO getDiagramRenderingData( + public RenderingDataDTO getCustomProfileViewRenderingData( @Parameter(description = "The name/url of the inquirer.") @RequestHeader( value = HttpHeaders.ORIGIN, diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/migration/MigrationContextRESTController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/migration/MigrationContextRESTController.java index 540e4d55..ee36fc27 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/migration/MigrationContextRESTController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/graphs/migration/MigrationContextRESTController.java @@ -28,6 +28,7 @@ import org.rdfarchitect.services.schemamigration.SetMigrationContextUseCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; @@ -55,7 +56,7 @@ public class MigrationContextRESTController { "Computes the diff of two given graphs and stores it in the session for later usage in migration endpoints. " + "Accepts the graphs either as file uploads, GraphIdentifiers or a combination of both.", tags = {"migration"}) - @PostMapping + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public void computeMigrationContext( @Parameter(description = "The name/url of the inquirer.") @RequestHeader(value = "origin", required = false, defaultValue = "unknown") diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/DatasetDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/DatasetDTO.java index e855f7f3..9a76c3be 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/DatasetDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/DatasetDTO.java @@ -19,7 +19,9 @@ import lombok.AllArgsConstructor; import lombok.Data; + import org.rdfarchitect.models.cim.data.dto.CIMPrefixPair; + import java.util.List; @Data diff --git a/backend/src/main/java/org/rdfarchitect/services/select/ListDatasetsUseCase.java b/backend/src/main/java/org/rdfarchitect/services/select/ListDatasetsUseCase.java index 642d5365..8bac8692 100644 --- a/backend/src/main/java/org/rdfarchitect/services/select/ListDatasetsUseCase.java +++ b/backend/src/main/java/org/rdfarchitect/services/select/ListDatasetsUseCase.java @@ -18,12 +18,15 @@ package org.rdfarchitect.services.select; import org.rdfarchitect.api.dto.DatasetDTO; + import java.util.List; public interface ListDatasetsUseCase { /** - * List all datasets including their prefixes and read-only status. Snapshots are excluded from the list. + * List all datasets including their prefixes and read-only status. Snapshots are excluded from + * the list. + * * @return List of dataset objects */ List listDatasets(); diff --git a/frontend/src/lib/GraphExport.svelte b/frontend/src/lib/GraphExport.svelte index 3173d777..1e59fb3b 100644 --- a/frontend/src/lib/GraphExport.svelte +++ b/frontend/src/lib/GraphExport.svelte @@ -20,10 +20,8 @@ import { onMount } from "svelte"; import { Fa } from "svelte-fa"; - import { BackendConnection } from "$lib/api/backend.js"; import { DropdownMenu } from "$lib/components/bitsui/dropdown/index"; import DatasetAndGraphSelection from "$lib/components/DatasetAndGraphSelection.svelte"; - import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; import { ReactiveOntology } from "$lib/models/reactive/models/ontology/reactive-ontology.svelte.js"; import { forceReloadTrigger } from "$lib/sharedState.svelte.js"; @@ -42,8 +40,6 @@ supportedMediaTypes = supportedRDFMediaTypes, } = $props(); - const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); - let selectedDatasetName = $state(null); let graphURI = $state(null); let selectedMediaType = $state(); @@ -75,7 +71,10 @@ $effect(async () => { if (selectedDatasetName && graphURI) { await ontologyStore.loadOntology(selectedDatasetName, graphURI); - const { data, error } = await ontologyStore.getOntologyForGraph(selectedDatasetName, graphURI); + const { data, error } = await ontologyStore.getOntologyForGraph( + selectedDatasetName, + graphURI, + ); if (error) { ontology = null; return; @@ -90,11 +89,11 @@ $effect(async () => { if (selectedDatasetName && graphURI && hasOntology) { - const res = await bec.generateOntologyEntries( + const { data } = await ontologyStore.generateOntologyEntries( selectedDatasetName, graphURI, ); - generatedOntologyEntries = await res.json(); + generatedOntologyEntries = data; generatedOntologyEntries.forEach(entry => (entry.generate = true)); return; } diff --git a/frontend/src/lib/api/apiGlobalUtils.js b/frontend/src/lib/api/apiGlobalUtils.js deleted file mode 100644 index c3d68a39..00000000 --- a/frontend/src/lib/api/apiGlobalUtils.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2024-2026 SOPTIM AG - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -import { BackendConnection } from "$lib/api/backend.js"; -import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; - -const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); - -export async function getXSDPrimitives() { - const res = await bec.getXSDPrimitives(); - return await res.json(); -} - -export async function getKnownFields() { - const res = await bec.getKnownOntologyFields(); - return await res.json(); -} diff --git a/frontend/src/lib/api/backend.js b/frontend/src/lib/api/backend.js deleted file mode 100644 index d0667e78..00000000 --- a/frontend/src/lib/api/backend.js +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (c) 2024-2026 SOPTIM AG - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; - -export class BackendConnection { - fetch; - url; - - constructor(fetch, url) { - this.fetch = fetch; - this.url = url; - } - - async fetchFilteredRenderingData(datasetName, graphURI, graphFilter) { - const url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/rendering`; - return fetch(url, { - method: "POST", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - body: JSON.stringify(graphFilter), - credentials: "include", - }); - } - - async getXSDPrimitives() { - const url = `${PUBLIC_BACKEND_URL}/primitiveDatatypes`; - return fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async getPrimitives(datasetName, graphURI) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/primitives`; - return await fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async getDataTypes(datasetName, graphURI) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/datatypes`; - return await fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async getStereotypes(datasetName, graphURI) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/stereotypes`; - return await fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async getDeleteRelation(datasetName, graphURI, resourceUuid) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/uuid/${encodeURIComponent(resourceUuid)}/deletion-impact`; - return await fetch(url, { - method: "GET", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async deleteResources(datasetName, graphURI, deleteRequests) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/delete-requests`; - return await fetch(url, { - method: "POST", - headers: new Headers({ "Content-Type": "application/json" }), - body: JSON.stringify(deleteRequests), - credentials: "include", - }); - } - - async getSearchResults(query, body) { - let url = `${PUBLIC_BACKEND_URL}/search?query=${encodeURIComponent(query)}`; - return await fetch(url, { - method: "POST", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - body: JSON.stringify(body), - credentials: "include", - }); - } - - async compareSchemas(datasetName, graphURI, file) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/compare`; - const formData = new FormData(); - formData.append("file", file); - return await fetch(url, { - method: "POST", - mode: "cors", - body: formData, - credentials: "include", - }); - } - - async compareDatasetSchemas( - datasetName, - graphURI, - otherDatasetName, - otherGraphURI, - ) { - const url = - `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}` + - `/graphs/${encodeURIComponent(graphURI)}/compare` + - `?otherDataset=${encodeURIComponent(otherDatasetName)}` + - `&otherGraph=${encodeURIComponent(otherGraphURI)}`; - - return fetch(url, { - method: "GET", - mode: "cors", - credentials: "include", - }); - } - - async compareSchemasFromFiles(fileA, fileB) { - const url = `${PUBLIC_BACKEND_URL}/compare`; - - const formData = new FormData(); - formData.append("fileA", fileA); - formData.append("fileB", fileB); - - return fetch(url, { - method: "POST", - mode: "cors", - body: formData, - credentials: "include", - }); - } - - async createSnapshot(datasetName) { - let url = `${PUBLIC_BACKEND_URL}/snapshots`; - return await fetch(url, { - method: "POST", - mode: "cors", - body: datasetName, - credentials: "include", - }); - } - - async loadSnapshot(base64Token) { - let url = `${PUBLIC_BACKEND_URL}/snapshots/${encodeURIComponent(base64Token)}`; - return await fetch(url, { - method: "GET", - mode: "cors", - credentials: "include", - }); - } - - async getChangelog(datasetName, graphURI) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/changes`; - return await fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async restoreVersion(datasetName, graphURI, version) { - console.log(`Restoring version ${version}`); - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/restore`; - return await fetch(url, { - method: "POST", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - body: version, - credentials: "include", - }); - } - - async updateClassPositions( - datasetName, - graphURI, - packageUUID, - classPositionDTOList, - ) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/layout/${encodeURIComponent(packageUUID)}/classes`; - return await fetch(url, { - method: "PUT", - headers: new Headers({ "Content-Type": "application/json" }), - mode: "cors", - body: JSON.stringify(classPositionDTOList), - credentials: "include", - }); - } - - async updateGlobalClassPositions( - datasetName, - packageUUID, - classPositionDTOList, - ) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/layout/${encodeURIComponent(packageUUID)}/classes`; - return await fetch(url, { - method: "PUT", - headers: new Headers({ "Content-Type": "application/json" }), - mode: "cors", - body: JSON.stringify(classPositionDTOList), - credentials: "include", - }); - } - - async getKnownOntologyFields() { - let url = `${PUBLIC_BACKEND_URL}/ontology-fields`; - return await fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async generateOntologyEntries(datasetName, graphURI) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/ontology/generate`; - return await fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async postCopyClass(datasetName, graphURI, classUUID, targetInfo) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/classes/${encodeURIComponent(classUUID)}/copy`; - return await fetch(url, { - method: "POST", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - body: JSON.stringify(targetInfo), - credentials: "include", - }); - } - - async extendClass(datasetName, graphURI, classUUID, body) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/classes/${encodeURIComponent(classUUID)}/extend`; - return await fetch(url, { - method: "POST", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - body: JSON.stringify(body), - credentials: "include", - }); - } - - async getCustomGraphDiagramRenderingData(datasetName, graphURI, diagramId) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/diagrams/${encodeURIComponent(diagramId)}`; - return await fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } - - async getCustomDatasetDiagramRenderingData(datasetName, diagramId) { - let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/diagrams/${encodeURIComponent(diagramId)}`; - return await fetch(url, { - method: "GET", - mode: "cors", - headers: new Headers({ "Content-Type": "application/json" }), - credentials: "include", - }); - } -} diff --git a/frontend/src/lib/components/DatasetAndGraphSelection.svelte b/frontend/src/lib/components/DatasetAndGraphSelection.svelte index c5309e6b..d6f93f21 100644 --- a/frontend/src/lib/components/DatasetAndGraphSelection.svelte +++ b/frontend/src/lib/components/DatasetAndGraphSelection.svelte @@ -50,7 +50,7 @@ } await graphStore.load(dataset); - graphNames = graphStore.getGraphURIs(dataset); + graphNames = graphStore.getGraphs(dataset); const valid = graphNames.some(graphName => getUri(graphName) === graph); if (!valid && !graphLocked) { graph = null; @@ -73,7 +73,7 @@ if (dataset) { await graphStore.load(dataset); - graphNames = graphStore.getGraphURIs(dataset); + graphNames = graphStore.getGraphs(dataset); } else { graphNames = []; } diff --git a/frontend/src/lib/rendering/svelteflow/components/SvelteFlowPaneContextMenu.svelte b/frontend/src/lib/rendering/svelteflow/components/SvelteFlowPaneContextMenu.svelte index 10844c9d..72954877 100644 --- a/frontend/src/lib/rendering/svelteflow/components/SvelteFlowPaneContextMenu.svelte +++ b/frontend/src/lib/rendering/svelteflow/components/SvelteFlowPaneContextMenu.svelte @@ -18,14 +18,13 @@ diff --git a/frontend/src/routes/changelog/ChangesRow.svelte b/frontend/src/routes/changelog/ChangesRow.svelte index 0abb216b..20d4e669 100644 --- a/frontend/src/routes/changelog/ChangesRow.svelte +++ b/frontend/src/routes/changelog/ChangesRow.svelte @@ -18,9 +18,8 @@ import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"; import { Fa } from "svelte-fa"; - import { BackendConnection } from "$lib/api/backend.js"; + import { restoreVersion } from "$lib/api/generated/index.ts"; import ButtonControl from "$lib/components/ButtonControl.svelte"; - import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; import { editorState, @@ -37,20 +36,18 @@ readonly, } = $props(); - const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); - const rowKey = $derived(`${change.changeId}::row`); const additionsKey = $derived(`${change.changeId}::additions`); const deletionsKey = $derived(`${change.changeId}::deletions`); - async function restoreVersion(changeId) { + async function callRestoreVersion(changeId) { console.log("restoreVersion", changeId); - const res = await bec.restoreVersion( + const { error } = await restoreVersion( editorState.selectedDataset.getValue(), editorState.selectedGraph.getValue(), changeId, ); - if (res.ok) { + if (!error) { console.log("Version restored successfully"); forceReloadTrigger.trigger(); toastStore.success( @@ -58,7 +55,7 @@ "The selected version has been restored.", ); } else { - console.error("Failed to restore version:", res.statusText); + console.error("Failed to restore version:", error); toastStore.error( "Restore failed", "Could not restore the selected version.", @@ -113,7 +110,7 @@ restoreVersion(change.changeId)} + callOnClick={() => callRestoreVersion(change.changeId)} > Restore Version diff --git a/frontend/src/routes/changelog/Navigation.svelte b/frontend/src/routes/changelog/Navigation.svelte index 8f95fb18..76bca51d 100644 --- a/frontend/src/routes/changelog/Navigation.svelte +++ b/frontend/src/routes/changelog/Navigation.svelte @@ -40,7 +40,8 @@ async function fetchNavigationObject() { await datasetStore.load(); const newDatasetList = []; - for (const datasetName of $datasetStore.data) { + for (const dataset of $datasetStore.data) { + const datasetName = dataset.label; let showDatasetContents = datasetName === selectedDatasetName; showDatasetContents |= datasetList.find( datasetObject => datasetObject.label === datasetName, @@ -52,7 +53,7 @@ }); await graphStore.load(datasetName); graphStore - .getGraphNames(datasetName) + .getGraphs(datasetName) .forEach(graphUri => newDatasetList.at(-1).graphs.push(graphUri), ); diff --git a/frontend/src/routes/compare/CompareDialog.svelte b/frontend/src/routes/compare/CompareDialog.svelte index 6718fc8c..3eed12d7 100644 --- a/frontend/src/routes/compare/CompareDialog.svelte +++ b/frontend/src/routes/compare/CompareDialog.svelte @@ -15,11 +15,14 @@ - --> {#if diagrams.length > 0} diff --git a/frontend/src/routes/mainpage/packageNavigation/ExtendClassDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/ExtendClassDialog.svelte index 3a05cc12..50826c37 100644 --- a/frontend/src/routes/mainpage/packageNavigation/ExtendClassDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/ExtendClassDialog.svelte @@ -16,16 +16,14 @@ --> diff --git a/frontend/src/routes/mainpage/packageNavigation/GraphSection.svelte b/frontend/src/routes/mainpage/packageNavigation/GraphSection.svelte index c13e850d..02391df9 100644 --- a/frontend/src/routes/mainpage/packageNavigation/GraphSection.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/GraphSection.svelte @@ -106,7 +106,10 @@ async function initialize() { await ontologyStore.loadOntology(datasetNavEntry.id, graphNavEntry.id); - const { data } = await ontologyStore.getOntologyForGraph(datasetNavEntry.id, graphNavEntry.id); + const { data } = await ontologyStore.getOntologyForGraph( + datasetNavEntry.id, + graphNavEntry.id, + ); ontology = data; canUndo = versionControlStore.canUndo( datasetNavEntry.id, diff --git a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDatasetDiagramDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDatasetDiagramDialog.svelte index e27cc2af..1e2a1639 100644 --- a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDatasetDiagramDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDatasetDiagramDialog.svelte @@ -73,22 +73,17 @@ } async function fetchGraphs() { - try { - await graphStore.load(lockedDatasetName); - graphs = graphStore - .getGraphURIs(lockedDatasetName) - .map(graph => { - return { - ...graph, - selected: false, - expanded: false, - }; - }) - .sort((a, b) => getUri(a).localeCompare(getUri(b))); - } catch (err) { - console.error("Failed to load graphs:", err); - graphs = []; - } + await graphStore.load(lockedDatasetName); + graphs = graphStore + .getGraphs(lockedDatasetName) + .map(graph => { + return { + ...graph, + selected: false, + expanded: false, + }; + }) + .sort((a, b) => getUri(a).localeCompare(getUri(b))); } async function createPackageMap() { diff --git a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/customDiagramDialogUtils.js b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/customDiagramDialogUtils.js index d51c8b90..613b9484 100644 --- a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/customDiagramDialogUtils.js +++ b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/customDiagramDialogUtils.js @@ -48,38 +48,34 @@ export async function createClassListForGraph( graphURI, selectedClasses, ) { - try { - await classStore.load(datasetName, graphURI); - const classList = await classStore.getClasses(datasetName, graphURI); + await classStore.load(datasetName, graphURI); + const classList = await classStore.getClasses(datasetName, graphURI); - const grouped = {}; + const grouped = {}; - for (const cls of classList) { - const packageId = getPackageId(cls.package); - if (!grouped[packageId]) { - grouped[packageId] = []; - } - - cls.selected = !!selectedClasses.some( - selected => selected.uuid === cls.uuid, - ); - - grouped[packageId].push({ - ...cls, - packageUUID: packageId, - }); + for (const cls of classList) { + const packageId = getPackageId(cls.package); + if (!grouped[packageId]) { + grouped[packageId] = []; } - for (const key of Object.keys(grouped)) { - grouped[key].sort((a, b) => - (a.label ?? "").localeCompare(b.label ?? "", undefined, { - sensitivity: "base", - }), - ); - } + cls.selected = !!selectedClasses.some( + selected => selected.uuid === cls.uuid, + ); - return grouped; - } catch (err) { - console.error("Failed to load classes:", err); + grouped[packageId].push({ + ...cls, + packageUUID: packageId, + }); } + + for (const key of Object.keys(grouped)) { + grouped[key].sort((a, b) => + (a.label ?? "").localeCompare(b.label ?? "", undefined, { + sensitivity: "base", + }), + ); + } + + return grouped; } diff --git a/frontend/src/routes/mainpage/packageNavigation/ontology-editor-dialog/AddKnownFieldsDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/ontology-editor-dialog/AddKnownFieldsDialog.svelte index 0f7a99c9..2c9fa6ac 100644 --- a/frontend/src/routes/mainpage/packageNavigation/ontology-editor-dialog/AddKnownFieldsDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/ontology-editor-dialog/AddKnownFieldsDialog.svelte @@ -15,11 +15,9 @@ - -->