mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 18:16:40 +00:00
convert codebase to typescript + introduce biome lint
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"files": {
|
||||
"ignore": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"public/pyodide",
|
||||
"**/*.svelte"
|
||||
]
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": [
|
||||
"src/modules/wasm/worker/**"
|
||||
],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
/**
|
||||
* svelte-preprocess cannot figure out whether you have
|
||||
* a value or a type, so tell TypeScript to enforce using
|
||||
* `import type` instead of `import` for Types.
|
||||
*/
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* To have warnings / errors of the Svelte compiler at the
|
||||
* correct position, enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable this if you'd like to use dynamic types.
|
||||
*/
|
||||
"checkJs": false,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"],
|
||||
"$src": ["./src"],
|
||||
"$src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Use global.d.ts instead of compilerOptions.types
|
||||
* to avoid limiting type declarations.
|
||||
*/
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
Generated
+258
@@ -16,6 +16,7 @@
|
||||
"svelte-spa-router": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
@@ -25,11 +26,13 @@
|
||||
"mode-watcher": "^1.1.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"svelte": "^5.53.6",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.4.1"
|
||||
}
|
||||
},
|
||||
@@ -47,6 +50,170 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
|
||||
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
"biome": "bin/biome"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "1.9.4",
|
||||
"@biomejs/cli-darwin-x64": "1.9.4",
|
||||
"@biomejs/cli-linux-arm64": "1.9.4",
|
||||
"@biomejs/cli-linux-arm64-musl": "1.9.4",
|
||||
"@biomejs/cli-linux-x64": "1.9.4",
|
||||
"@biomejs/cli-linux-x64-musl": "1.9.4",
|
||||
"@biomejs/cli-win32-arm64": "1.9.4",
|
||||
"@biomejs/cli-win32-x64": "1.9.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
|
||||
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
|
||||
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.1.tgz",
|
||||
@@ -1468,6 +1635,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT/X11"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||
@@ -2127,6 +2310,16 @@
|
||||
"svelte": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -2201,6 +2394,20 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/regexparam": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz",
|
||||
@@ -2282,6 +2489,19 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sade": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
||||
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mri": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded": {
|
||||
"version": "1.89.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.0.tgz",
|
||||
@@ -2801,6 +3021,30 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz",
|
||||
"integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"chokidar": "^4.0.1",
|
||||
"fdir": "^6.2.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"sade": "^1.7.4"
|
||||
},
|
||||
"bin": {
|
||||
"svelte-check": "bin/svelte-check"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^4.0.0 || ^5.0.0-next.0",
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-sonner": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz",
|
||||
@@ -3001,6 +3245,20 @@
|
||||
"url": "https://github.com/sponsors/Wombosvideo"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"check": "tsc -p tsconfig.json --noEmit && svelte-check && biome lint .",
|
||||
"lint": "biome lint .",
|
||||
"lint:fix": "biome lint --write .",
|
||||
"preview": "vite preview",
|
||||
"deploy": "npm run build && npx wrangler pages deploy dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
@@ -19,10 +23,12 @@
|
||||
"mode-watcher": "^1.1.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"svelte": "^5.53.6",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"vite": "^6.4.1"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Router from 'svelte-spa-router';
|
||||
import routes from './routes';
|
||||
</script>
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare module "*.svelte" {
|
||||
import type { SvelteComponent } from "svelte";
|
||||
export default class Component extends SvelteComponent {}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as Menubar from "$lib/components/ui/menubar";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import * as API from "$src/modules/api/api.svelte.js";
|
||||
import { error, success, info } from "$src/modules/utils/toast.svelte.js";
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import * as API from "$src/modules/api/api.svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
|
||||
let { isOpen = false } = $props();
|
||||
let { isOpen = false } : { isOpen?: boolean } = $props();
|
||||
|
||||
function openForum() {
|
||||
window.open('https://community.osarch.org', '_blank');
|
||||
@@ -23,8 +23,9 @@
|
||||
try {
|
||||
await IDS.openDocument();
|
||||
} catch (err) {
|
||||
if (err.message !== 'File selection cancelled') {
|
||||
error('Error opening file: ' + err.message);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (message !== 'File selection cancelled') {
|
||||
error(`Error opening file: ${message}`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
@@ -50,7 +51,7 @@
|
||||
success('Audit completed successfully');
|
||||
} catch (err) {
|
||||
console.error("Audit failed: ", err);
|
||||
error(`Audit failed: check console for details`);
|
||||
error("Audit failed: check console for details");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -142,4 +143,4 @@
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { Module } from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import { Module } from "$src/modules/api/ids.svelte";
|
||||
</script>
|
||||
|
||||
<div class="app-ribbon">
|
||||
@@ -20,4 +19,4 @@
|
||||
<span>Error</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as Tooltip from "$lib/components/ui/tooltip";
|
||||
import { IFCModels, loadIfc, unloadIfc, auditIfc, openIfc, createAuditReport, clearIdsAuditReports, runAudit } from "$src/modules/api/api.svelte.js";
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
||||
import { IFCModels, openIfc, unloadIfc, runAudit } from "$src/modules/api/api.svelte";
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
import { ChevronRightIcon, LinkIcon, XIcon } from "@lucide/svelte";
|
||||
import { Bonsai, connect, disconnect, runAudit as runBonsaiAudit } from "$src/modules/api/bonsai.svelte.js";
|
||||
import { Bonsai, connect, disconnect, runAudit as runBonsaiAudit } from "$src/modules/api/bonsai.svelte";
|
||||
import { onMount } from 'svelte';
|
||||
import type { AuditReport } from "$src/types/report";
|
||||
|
||||
let isAuditing = $state(false);
|
||||
let activeTab = $state('home');
|
||||
let activeTab = $state<"home" | "bonsai">('home');
|
||||
// biome-ignore lint/style/useConst: Svelte state uses assignment for updates.
|
||||
let isMinimized = $state(false);
|
||||
|
||||
const handleLoadModel = async () => {
|
||||
@@ -16,15 +18,17 @@
|
||||
await openIfc();
|
||||
success('IFC model loaded successfully');
|
||||
} catch (err) {
|
||||
error(`Failed to load IFC model: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to load IFC model: ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnloadModel = async (modelId) => {
|
||||
const handleUnloadModel = async (modelId: string) => {
|
||||
try {
|
||||
await unloadIfc(modelId);
|
||||
} catch (err) {
|
||||
error(`Failed to unload model: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to unload model: ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,14 +39,14 @@
|
||||
success('Audit completed successfully');
|
||||
} catch (err) {
|
||||
console.error("Audit failed: ", err);
|
||||
error(`Audit failed: check console for details`);
|
||||
error("Audit failed: check console for details");
|
||||
} finally {
|
||||
isAuditing = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewAuditReport = (auditId) => {
|
||||
const auditReport = IFCModels.audits.find(audit => audit.id === auditId);
|
||||
const handleViewAuditReport = (auditId: string) => {
|
||||
const auditReport = IFCModels.audits.find(audit => audit.id === auditId) as AuditReport | undefined;
|
||||
if (!auditReport) return;
|
||||
|
||||
// Switch to the IDS document that was used for this audit
|
||||
@@ -58,7 +62,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
const formatFileSize = (bytes: number) => {
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
let size = bytes;
|
||||
let unitIndex = 0;
|
||||
@@ -91,7 +95,7 @@
|
||||
<div class="buttons">
|
||||
<Tooltip.Provider>
|
||||
{#if isMinimized}
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn expand-btn" onclick={() => isMinimized = false} aria-label="Expand Toolbar">
|
||||
<ChevronRightIcon size={24} />
|
||||
@@ -102,7 +106,7 @@
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn {activeTab === 'home' ? 'active' : ''}" onclick={() => activeTab = 'home'} aria-label="Home">
|
||||
<svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
@@ -114,7 +118,7 @@
|
||||
<p>Home</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn {activeTab === 'bonsai' ? 'active' : ''}" onclick={() => activeTab = 'bonsai'} aria-label="Bonsai Integration">
|
||||
<svg style="height: 20px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32mm" height="32mm" version="1.1" viewBox="0 0 32 32" xml:space="preserve">
|
||||
@@ -692,4 +696,4 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
|
||||
let { addFacet } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
const { addFacet } : { addFacet: (facetType: FacetType) => void } = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -63,4 +65,4 @@
|
||||
Part Of
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import type { IdsDocument } from "$src/types/ids";
|
||||
|
||||
function switchDocument(docId) {
|
||||
function switchDocument(docId: string) {
|
||||
IDS.Module.activeDocument = docId;
|
||||
}
|
||||
|
||||
function closeDocument(docId) {
|
||||
function closeDocument(docId: string) {
|
||||
IDS.deleteDocument(docId);
|
||||
}
|
||||
|
||||
const handleActivation = (event: KeyboardEvent, action: () => void) => {
|
||||
if (event.currentTarget !== event.target) return;
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
action();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="ids-tabs">
|
||||
@@ -15,10 +24,13 @@
|
||||
<div
|
||||
class="ids-tab"
|
||||
class:active={IDS.Module.activeDocument === docId}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => switchDocument(docId)}
|
||||
aria-label={doc.info.title || "Untitled"}
|
||||
onkeydown={(event) => handleActivation(event, () => switchDocument(docId))}
|
||||
aria-label={(doc as IdsDocument).info.title || "Untitled"}
|
||||
>
|
||||
<span class="tab-title">{doc.info.title || "Untitled"}</span>
|
||||
<span class="tab-title">{(doc as IdsDocument).info.title || "Untitled"}</span>
|
||||
<button
|
||||
class="tab-close"
|
||||
onclick={(e) => { e.stopPropagation(); closeDocument(docId); }}
|
||||
@@ -31,4 +43,4 @@
|
||||
</div>
|
||||
{/each}
|
||||
<div class="filler-tab"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -576,7 +576,8 @@ html, body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
label,
|
||||
.form-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import XIcon from "@lucide/svelte/icons/x";
|
||||
import * as Dialog from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import * as Dialog from "./index";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
portalProps?: Record<string, unknown>;
|
||||
children?: Snippet;
|
||||
showCloseButton?: boolean;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -11,7 +20,7 @@
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Portal {...portalProps}>
|
||||
@@ -35,4 +44,4 @@
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</DialogPrimitive.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Portal>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Description
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dialog-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -15,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -16,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Overlay
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Title
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dialog-title"
|
||||
class={cn("text-lg leading-none", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
|
||||
+12
-4
@@ -1,8 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
checked?: boolean;
|
||||
indeterminate?: boolean;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
@@ -10,7 +18,7 @@
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
@@ -34,4 +42,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
|
||||
+11
-4
@@ -1,14 +1,21 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
sideOffset?: number;
|
||||
portalProps?: Record<string, unknown>;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||
@@ -22,4 +29,4 @@
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.GroupHeading
|
||||
@@ -15,4 +20,4 @@
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
variant?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
@@ -21,4 +28,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,12 +1,20 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -17,4 +25,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+8
-3
@@ -1,11 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
value?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
@@ -13,4 +18,4 @@
|
||||
bind:value
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,14 +1,22 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
value: string;
|
||||
children?: Snippet<[ { checked: boolean } ]>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
@@ -28,4 +36,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+11
-4
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
@@ -16,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,7 +1,15 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -9,7 +17,7 @@
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
@@ -24,4 +32,4 @@
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRightIcon class="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
|
||||
+7
-3
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
||||
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
checked?: boolean;
|
||||
indeterminate?: boolean;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
@@ -10,7 +18,7 @@
|
||||
indeterminate = $bindable(false),
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
@@ -34,4 +42,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Align = "start" | "center" | "end";
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
sideOffset?: number;
|
||||
alignOffset?: number;
|
||||
align?: Align;
|
||||
side?: Side;
|
||||
portalProps?: Record<string, unknown>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -11,7 +24,7 @@
|
||||
side = "bottom",
|
||||
portalProps,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Portal {...portalProps}>
|
||||
@@ -28,4 +41,4 @@
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
</MenubarPrimitive.Portal>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
inset,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.GroupHeading
|
||||
@@ -15,4 +20,4 @@
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Group bind:ref data-slot="menubar-group" {...restProps} />
|
||||
<MenubarPrimitive.Group bind:ref data-slot="menubar-group" {...restProps} />
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
variant?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -8,7 +15,7 @@
|
||||
inset = undefined,
|
||||
variant = "default",
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Item
|
||||
@@ -21,4 +28,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
inset,
|
||||
children,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -17,4 +24,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
value: string;
|
||||
children?: Snippet<[ { checked: boolean } ]>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.RadioItem
|
||||
@@ -28,4 +36,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.RadioItem>
|
||||
</MenubarPrimitive.RadioItem>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Separator
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="menubar-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
@@ -16,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.SubContent
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -9,7 +17,7 @@
|
||||
inset = undefined,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.SubTrigger
|
||||
@@ -24,4 +32,4 @@
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRightIcon class="ml-auto size-4" />
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Trigger
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Root
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Toaster as Sonner } from "svelte-sonner";
|
||||
import { mode } from "mode-watcher";
|
||||
|
||||
let { ...restProps } = $props();
|
||||
let { ...restProps } : Record<string, unknown> = $props();
|
||||
</script>
|
||||
|
||||
<Sonner
|
||||
@@ -10,4 +10,4 @@
|
||||
class="toaster group"
|
||||
style="--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border);"
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
sideOffset?: number;
|
||||
side?: Side;
|
||||
children?: Snippet;
|
||||
arrowClasses?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -10,7 +22,7 @@
|
||||
children,
|
||||
arrowClasses,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Portal>
|
||||
@@ -42,4 +54,4 @@
|
||||
{/snippet}
|
||||
</TooltipPrimitive.Arrow>
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
</TooltipPrimitive.Portal>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
|
||||
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { clsx, } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -0,0 +1,7 @@
|
||||
import { clsx } from "clsx";
|
||||
import type { ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { mount } from 'svelte';
|
||||
import './css/app.css';
|
||||
import App from './App.svelte';
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('root'),
|
||||
});
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { mount } from 'svelte';
|
||||
import './css/app.css';
|
||||
import App from './App.svelte';
|
||||
|
||||
const root = document.getElementById('root');
|
||||
if (!root) {
|
||||
throw new Error('Missing root element');
|
||||
}
|
||||
|
||||
const app = mount(App, {
|
||||
target: root,
|
||||
});
|
||||
|
||||
export default app;
|
||||
+76
-37
@@ -1,8 +1,31 @@
|
||||
import wasm from "$src/modules/wasm";
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import hyperid from "hyperid";
|
||||
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||
import type { IdsDocument } from "$src/types/ids";
|
||||
|
||||
export let Autocompletions = $state({
|
||||
type AutocompletionState = {
|
||||
entityClasses: string[];
|
||||
materialCategories: string[];
|
||||
classificationSystems: Record<string, { source: string; tokens: string[] | null }>;
|
||||
dataTypes: string[];
|
||||
isLoaded: boolean;
|
||||
};
|
||||
|
||||
type IfcModel = {
|
||||
id: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
loadedAt: Date;
|
||||
};
|
||||
|
||||
type IfcModelState = {
|
||||
models: IfcModel[];
|
||||
isLoading: boolean;
|
||||
audits: AuditReport[];
|
||||
};
|
||||
|
||||
export const Autocompletions: AutocompletionState = $state({
|
||||
entityClasses: [],
|
||||
materialCategories: [],
|
||||
classificationSystems: {},
|
||||
@@ -10,13 +33,13 @@ export let Autocompletions = $state({
|
||||
isLoaded: false
|
||||
});
|
||||
|
||||
export let IFCModels = $state({
|
||||
export const IFCModels: IfcModelState = $state({
|
||||
models: [],
|
||||
isLoading: false,
|
||||
audits: []
|
||||
});
|
||||
|
||||
const id = hyperid();
|
||||
const id: () => string = hyperid();
|
||||
|
||||
// Preload autocompletions on initialization
|
||||
wasm.init().then(async () => {
|
||||
@@ -30,26 +53,30 @@ export async function preloadAutocompletions() {
|
||||
// Entity classes
|
||||
const entitySets = await Promise.all(
|
||||
schemas.map(schema => wasm.getAllEntityClasses(schema))
|
||||
);
|
||||
const allEntities = new Set();
|
||||
entitySets.forEach(entities => {
|
||||
entities.forEach(entity => allEntities.add(entity.toUpperCase()));
|
||||
});
|
||||
) as string[][];
|
||||
const allEntities = new Set<string>();
|
||||
for (const entities of entitySets) {
|
||||
for (const entity of entities) {
|
||||
allEntities.add(entity.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
// Data types
|
||||
const dataTypeSets = await Promise.all(
|
||||
schemas.map(schema => wasm.getAllDataTypes(schema))
|
||||
);
|
||||
const allDataTypes = new Set();
|
||||
dataTypeSets.forEach(dataTypes => {
|
||||
Object.keys(dataTypes).forEach(dataType => allDataTypes.add(dataType));
|
||||
});
|
||||
) as Record<string, string>[];
|
||||
const allDataTypes = new Set<string>();
|
||||
for (const dataTypes of dataTypeSets) {
|
||||
for (const dataType of Object.keys(dataTypes as Record<string, string>)) {
|
||||
allDataTypes.add(dataType);
|
||||
}
|
||||
}
|
||||
|
||||
// Material categories and Classification systems
|
||||
const [materialCategories, classificationSystems] = await Promise.all([
|
||||
wasm.getMaterialCategories(),
|
||||
wasm.getStandardClassificationSystems()
|
||||
]);
|
||||
]) as [string[], AutocompletionState["classificationSystems"]];
|
||||
|
||||
// Cache autocompletions
|
||||
Autocompletions.entityClasses = Array.from(allEntities).sort();
|
||||
@@ -64,15 +91,15 @@ export async function preloadAutocompletions() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPredefinedTypes(schema, entity) {
|
||||
export async function getPredefinedTypes(schema: string, entity: string) {
|
||||
return await wasm.getPredefinedTypes(schema, entity);
|
||||
}
|
||||
|
||||
export async function getEntityAttributes(schema, entity) {
|
||||
export async function getEntityAttributes(schema: string, entity: string) {
|
||||
return await wasm.getEntityAttributes(schema, entity);
|
||||
}
|
||||
|
||||
export async function getApplicablePsets(schema, entity, predefinedType = '') {
|
||||
export async function getApplicablePsets(schema: string, entity: string, predefinedType = '') {
|
||||
return await wasm.getApplicablePsets(schema, entity, predefinedType);
|
||||
}
|
||||
|
||||
@@ -92,7 +119,7 @@ export function getDataTypes() {
|
||||
return Autocompletions.dataTypes;
|
||||
}
|
||||
|
||||
export async function loadIfc(file) {
|
||||
export async function loadIfc(file: File): Promise<IfcModel> {
|
||||
try {
|
||||
IFCModels.isLoading = true;
|
||||
|
||||
@@ -100,10 +127,10 @@ export async function loadIfc(file) {
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
// Load IFC model
|
||||
const ifcId = await wasm.loadIfc(Array.from(uint8Array));
|
||||
const ifcId = await wasm.loadIfc(Array.from(uint8Array)) as string;
|
||||
|
||||
// Add to models list
|
||||
const model = {
|
||||
const model: IfcModel = {
|
||||
id: ifcId,
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
@@ -121,7 +148,7 @@ export async function loadIfc(file) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function unloadIfc(modelId) {
|
||||
export async function unloadIfc(modelId: string) {
|
||||
try {
|
||||
// Unload model
|
||||
await wasm.unloadIfc(modelId);
|
||||
@@ -136,9 +163,9 @@ export async function unloadIfc(modelId) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function auditIfc(modelId, idsData) {
|
||||
export async function auditIfc(modelId: string, idsData: string | Uint8Array | ArrayBuffer) {
|
||||
try {
|
||||
let idsBytes;
|
||||
let idsBytes: Uint8Array;
|
||||
if (typeof idsData === 'string') {
|
||||
idsBytes = new TextEncoder().encode(idsData);
|
||||
} else if (idsData instanceof ArrayBuffer) {
|
||||
@@ -148,7 +175,7 @@ export async function auditIfc(modelId, idsData) {
|
||||
}
|
||||
|
||||
// Run audit
|
||||
const auditResult = await wasm.auditIfc(modelId, idsBytes);
|
||||
const auditResult = await wasm.auditIfc(modelId, idsBytes) as { json: AuditReportData; html: string };
|
||||
|
||||
console.log(`Audit completed for model ${modelId}`);
|
||||
return auditResult;
|
||||
@@ -163,13 +190,14 @@ export function getLoadedModels() {
|
||||
}
|
||||
|
||||
export async function openIfc() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.ifc';
|
||||
|
||||
fileInput.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
const target = event.target as HTMLInputElement | null;
|
||||
const file = target?.files?.[0];
|
||||
if (!file) {
|
||||
reject(new Error('No file selected'));
|
||||
return;
|
||||
@@ -194,15 +222,20 @@ export async function openIfc() {
|
||||
});
|
||||
}
|
||||
|
||||
export function getIfcById(modelId) {
|
||||
export function getIfcById(modelId: string) {
|
||||
return IFCModels.models.find(model => model.id === modelId);
|
||||
}
|
||||
|
||||
export function createAuditReport(modelId, document, auditData, htmlReport = null) {
|
||||
export function createAuditReport(
|
||||
modelId: string,
|
||||
document: string,
|
||||
auditData: AuditReportData,
|
||||
htmlReport: string | null = null
|
||||
): AuditReport | undefined {
|
||||
const model = getIfcById(modelId);
|
||||
if (!model) return;
|
||||
|
||||
const auditReport = {
|
||||
const auditReport: AuditReport = {
|
||||
id: id(),
|
||||
modelId: modelId,
|
||||
modelName: model.fileName,
|
||||
@@ -216,19 +249,19 @@ export function createAuditReport(modelId, document, auditData, htmlReport = nul
|
||||
return auditReport;
|
||||
}
|
||||
|
||||
export function getAuditReportsForIfc(modelId) {
|
||||
export function getAuditReportsForIfc(modelId: string) {
|
||||
return IFCModels.audits.filter(audit => audit.modelId === modelId);
|
||||
}
|
||||
|
||||
export function getAuditReportById(auditId) {
|
||||
export function getAuditReportById(auditId: string) {
|
||||
return IFCModels.audits.find(audit => audit.id === auditId);
|
||||
}
|
||||
|
||||
export function clearIdsAuditReports(document) {
|
||||
export function clearIdsAuditReports(document: string) {
|
||||
IFCModels.audits = IFCModels.audits.filter(audit => audit.document !== document);
|
||||
}
|
||||
|
||||
export async function downloadAuditReport(auditId) {
|
||||
export async function downloadAuditReport(auditId: string) {
|
||||
const audit = getAuditReportById(auditId);
|
||||
if (!audit || !audit.htmlReport) {
|
||||
throw new Error('HTML report not available for this audit');
|
||||
@@ -237,7 +270,7 @@ export async function downloadAuditReport(auditId) {
|
||||
// Get IDS document title for filename
|
||||
let filename = 'report.html';
|
||||
if (audit.document && IDS.Module.documents[audit.document]) {
|
||||
const doc = IDS.Module.documents[audit.document];
|
||||
const doc = IDS.Module.documents[audit.document] as IdsDocument;
|
||||
const title = doc.info?.title || 'untitled';
|
||||
filename = `report_${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.html`;
|
||||
}
|
||||
@@ -270,9 +303,12 @@ export async function runAudit() {
|
||||
|
||||
// Get the active IDS document XML
|
||||
const idsXml = await IDS.exportActiveDocument();
|
||||
if (!idsXml) {
|
||||
throw new Error('Failed to export IDS document');
|
||||
}
|
||||
|
||||
// Run audit on all loaded models
|
||||
let firstAuditReport = null;
|
||||
let firstAuditReport: AuditReport | undefined;
|
||||
for (const model of IFCModels.models) {
|
||||
const result = await auditIfc(model.id, idsXml);
|
||||
|
||||
@@ -280,7 +316,10 @@ export async function runAudit() {
|
||||
const jsonData = result.json || null;
|
||||
const htmlReport = result.html || null;
|
||||
|
||||
const auditReport = createAuditReport(model.id, IDS.Module.activeDocument, jsonData, htmlReport);
|
||||
if (!jsonData) {
|
||||
continue;
|
||||
}
|
||||
const auditReport = createAuditReport(model.id, IDS.Module.activeDocument as string, jsonData, htmlReport);
|
||||
|
||||
// Store the first audit report to open in viewer
|
||||
if (!firstAuditReport) {
|
||||
+60
-22
@@ -1,12 +1,37 @@
|
||||
import { io } from 'socket.io-client';
|
||||
import { IFCModels } from './api.svelte.js';
|
||||
import * as IDS from './ids.svelte.js';
|
||||
import { error, success } from '../utils/toast.svelte.js';
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import { IFCModels } from './api.svelte';
|
||||
import * as IDS from './ids.svelte';
|
||||
import { error, success } from '../utils/toast.svelte';
|
||||
import hyperid from 'hyperid';
|
||||
import { onMount } from 'svelte';
|
||||
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||
|
||||
// Bonsai connection state
|
||||
export let Bonsai = $state({
|
||||
type BonsaiState = {
|
||||
enabled: boolean;
|
||||
port: string | null;
|
||||
socket: Socket | null;
|
||||
connected: boolean;
|
||||
auditing: boolean;
|
||||
};
|
||||
|
||||
type PendingAudit = {
|
||||
resolve: (value: string | null) => void;
|
||||
reject: (reason?: unknown) => void;
|
||||
};
|
||||
|
||||
type AuditResultPayload = {
|
||||
id?: string;
|
||||
json_report?: string;
|
||||
html_report?: string;
|
||||
};
|
||||
|
||||
type AuditErrorPayload = {
|
||||
id?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export const Bonsai: BonsaiState = $state({
|
||||
enabled: false,
|
||||
port: null,
|
||||
socket: null,
|
||||
@@ -14,8 +39,8 @@ export let Bonsai = $state({
|
||||
auditing: false
|
||||
});
|
||||
|
||||
const id = hyperid();
|
||||
const pendingAudits = new Map();
|
||||
const id: () => string = hyperid();
|
||||
const pendingAudits = new Map<string, PendingAudit>();
|
||||
|
||||
// Check for Bonsai server port in URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -29,8 +54,11 @@ if (serverPort) {
|
||||
/**
|
||||
* Connect to Bonsai server
|
||||
*/
|
||||
export const connect = () => new Promise((resolve, reject) => {
|
||||
if (!Bonsai.port) return;
|
||||
export const connect = () => new Promise<void>((resolve, reject) => {
|
||||
if (!Bonsai.port) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Bonsai.socket = io(`ws://127.0.0.1:${Bonsai.port}/ifctester`, {
|
||||
@@ -49,7 +77,7 @@ export const connect = () => new Promise((resolve, reject) => {
|
||||
Bonsai.connected = false;
|
||||
});
|
||||
|
||||
Bonsai.socket.on('connect_error', (err) => {
|
||||
Bonsai.socket.on('connect_error', (err: Error) => {
|
||||
Bonsai.connected = false;
|
||||
error(`Failed to connect to Bonsai: ${err.message}`);
|
||||
reject(err);
|
||||
@@ -59,7 +87,8 @@ export const connect = () => new Promise((resolve, reject) => {
|
||||
Bonsai.socket.on('error', handleAuditError);
|
||||
|
||||
} catch (err) {
|
||||
error(`Failed to connect to Bonsai: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to connect to Bonsai: ${message}`);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
@@ -93,14 +122,21 @@ export const runAudit = async () => {
|
||||
|
||||
// Convert IDS document to XML string
|
||||
const idsXml = await IDS.exportActiveDocument();
|
||||
if (!idsXml) {
|
||||
throw new Error('Failed to export IDS document');
|
||||
}
|
||||
|
||||
const requestId = id();
|
||||
const socket = Bonsai.socket;
|
||||
if (!socket) {
|
||||
throw new Error('Bonsai socket not connected');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<string | null>((resolve, reject) => {
|
||||
// Store request with resolve/reject functions
|
||||
pendingAudits.set(requestId, { resolve, reject });
|
||||
|
||||
Bonsai.socket.emit('audit_ids', {
|
||||
socket.emit('audit_ids', {
|
||||
id: requestId,
|
||||
ids: idsXml
|
||||
});
|
||||
@@ -108,7 +144,8 @@ export const runAudit = async () => {
|
||||
|
||||
} catch (err) {
|
||||
Bonsai.auditing = false;
|
||||
error(`Failed to run Bonsai audit: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to run Bonsai audit: ${message}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -117,7 +154,7 @@ export const runAudit = async () => {
|
||||
* Handles audit results from Bonsai server
|
||||
* @param {Object} data - Audit result data
|
||||
*/
|
||||
const handleAuditResult = (data) => {
|
||||
const handleAuditResult = (data: AuditResultPayload) => {
|
||||
if (!data.id || !data.json_report) return;
|
||||
|
||||
const pendingAudit = pendingAudits.get(data.id);
|
||||
@@ -130,13 +167,14 @@ const handleAuditResult = (data) => {
|
||||
const { resolve } = pendingAudit;
|
||||
|
||||
try {
|
||||
const reportData = JSON.parse(data.json_report);
|
||||
const reportData = JSON.parse(data.json_report) as AuditReportData;
|
||||
|
||||
const auditReport = {
|
||||
const auditReport: AuditReport = {
|
||||
id: data.id,
|
||||
modelId: `bonsai:${data.id}`,
|
||||
date: new Date().toISOString(),
|
||||
modelName: 'Bonsai IFC Model',
|
||||
document: IDS.Module.activeDocument,
|
||||
document: IDS.Module.activeDocument ?? "",
|
||||
data: reportData,
|
||||
htmlReport: data.html_report
|
||||
};
|
||||
@@ -152,7 +190,8 @@ const handleAuditResult = (data) => {
|
||||
|
||||
} catch (err) {
|
||||
Bonsai.auditing = false;
|
||||
error(`Failed to process audit result: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to process audit result: ${message}`);
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
@@ -161,7 +200,7 @@ const handleAuditResult = (data) => {
|
||||
* Handles audit errors from Bonsai server
|
||||
* @param {Object} data - Error data
|
||||
*/
|
||||
const handleAuditError = (data) => {
|
||||
const handleAuditError = (data: AuditErrorPayload) => {
|
||||
if (!data.id) return;
|
||||
|
||||
const pendingAudit = pendingAudits.get(data.id);
|
||||
@@ -174,7 +213,6 @@ const handleAuditError = (data) => {
|
||||
const { resolve } = pendingAudit;
|
||||
|
||||
Bonsai.auditing = false;
|
||||
error(`Audit failed (Bonsai): ${data.error}`);
|
||||
error(`Audit failed (Bonsai): ${data.error ?? "Unknown error"}`);
|
||||
resolve(null);
|
||||
};
|
||||
|
||||
+125
-86
@@ -1,10 +1,18 @@
|
||||
import wasm from "$src/modules/wasm";
|
||||
import { clearIdsAuditReports } from "./api.svelte.js";
|
||||
import { clearIdsAuditReports } from "./api.svelte";
|
||||
import hyperid from "hyperid";
|
||||
import {tick} from "svelte";
|
||||
import type { DocumentState, Facet, FacetValue, IdsDocument, IdsCardinality, Restriction, Specification } from "$src/types/ids";
|
||||
|
||||
export let Module = $state({
|
||||
documents: [],
|
||||
type ModuleState = {
|
||||
documents: Record<string, IdsDocument>;
|
||||
activeDocument: string | null;
|
||||
status: "loading" | "ready" | "error";
|
||||
states: Record<string, DocumentState>;
|
||||
};
|
||||
|
||||
export const Module: ModuleState = $state({
|
||||
documents: {},
|
||||
activeDocument: null,
|
||||
status: "loading",
|
||||
states: {}
|
||||
@@ -17,14 +25,15 @@ wasm.init().then(() => {
|
||||
Module.status = "error";
|
||||
});
|
||||
|
||||
const id = hyperid()
|
||||
const id: () => string = hyperid();
|
||||
|
||||
export function setDocumentState(docId, updates) {
|
||||
export function setDocumentState(docId: string, updates: Partial<DocumentState>) {
|
||||
if (!Module.states[docId]) {
|
||||
Module.states[docId] = {
|
||||
activeTab: 'info',
|
||||
viewMode: 'editor',
|
||||
activeSpecification: null
|
||||
activeSpecification: null,
|
||||
auditReport: null
|
||||
};
|
||||
}
|
||||
Object.assign(Module.states[docId], updates);
|
||||
@@ -32,7 +41,7 @@ export function setDocumentState(docId, updates) {
|
||||
|
||||
export async function createDocument() {
|
||||
const docId = id();
|
||||
const doc = await wasm.createIDS();
|
||||
const doc = await wasm.createIDS() as IdsDocument;
|
||||
|
||||
Module.documents[docId] = doc;
|
||||
|
||||
@@ -43,14 +52,14 @@ export async function createDocument() {
|
||||
Module.activeDocument = docId;
|
||||
}
|
||||
|
||||
export async function deleteDocument(id) {
|
||||
export async function deleteDocument(id: string) {
|
||||
// Clear any audit reports generated using this IDS document
|
||||
clearIdsAuditReports(id);
|
||||
|
||||
delete Module.documents[id];
|
||||
delete Module.states[id];
|
||||
|
||||
if (Module.activeDocument == id) {
|
||||
if (Module.activeDocument === id) {
|
||||
// If there are other documents, set the first one as active
|
||||
if (Object.keys(Module.documents).length > 0) {
|
||||
Module.activeDocument = Object.keys(Module.documents)[0];
|
||||
@@ -62,19 +71,19 @@ export async function deleteDocument(id) {
|
||||
|
||||
// Normalize (remove xs: prefix) from JSON dict returned from Python
|
||||
// We need this because the backend exports with xs: prefix, yet expects a dict without prefixes.
|
||||
function normalizeIdsDict(obj) {
|
||||
function normalizeIdsDict(obj: unknown): unknown {
|
||||
if (typeof obj !== 'object' || obj === null) return obj;
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(normalizeIdsDict);
|
||||
}
|
||||
|
||||
const result = {};
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (key === 'xs:restriction' && Array.isArray(value) && value.length > 0) {
|
||||
// Convert xs:restriction array to restriction object
|
||||
const restriction = value[0];
|
||||
const newRestriction = {};
|
||||
const restriction = value[0] as Record<string, unknown>;
|
||||
const newRestriction: Record<string, unknown> = {};
|
||||
|
||||
for (const [restrictionKey, restrictionValue] of Object.entries(restriction)) {
|
||||
if (restrictionKey.startsWith('xs:')) {
|
||||
@@ -86,7 +95,7 @@ function normalizeIdsDict(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
result['restriction'] = newRestriction;
|
||||
result.restriction = newRestriction;
|
||||
} else {
|
||||
result[key] = normalizeIdsDict(value);
|
||||
}
|
||||
@@ -96,13 +105,16 @@ function normalizeIdsDict(obj) {
|
||||
}
|
||||
|
||||
export async function openDocument() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileInput = document.createElement('input');
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const fileInput = document.createElement('input') as HTMLInputElement & {
|
||||
oncancel?: ((this: HTMLInputElement, ev: Event) => void) | null;
|
||||
};
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.ids,.xml';
|
||||
|
||||
fileInput.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
const target = event.target as HTMLInputElement | null;
|
||||
const file = target?.files?.[0];
|
||||
if (!file) {
|
||||
reject(new Error('No file selected'));
|
||||
return;
|
||||
@@ -112,8 +124,8 @@ export async function openDocument() {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
const fileContent = e.target.result;
|
||||
const doc = normalizeIdsDict(await wasm.openIDS(fileContent, false));
|
||||
const fileContent = (e.target as FileReader).result;
|
||||
const doc = normalizeIdsDict(await wasm.openIDS(String(fileContent), false)) as IdsDocument;
|
||||
const docId = id();
|
||||
|
||||
// Add document to list and set as active
|
||||
@@ -145,16 +157,16 @@ export async function openDocument() {
|
||||
});
|
||||
}
|
||||
|
||||
export async function exportActiveDocument() {
|
||||
export async function exportActiveDocument(): Promise<string | null> {
|
||||
if (!Module.activeDocument) return null;
|
||||
|
||||
const doc = $state.snapshot(Module.documents[Module.activeDocument]);
|
||||
const xmlString = await wasm.exportIDS(doc);
|
||||
const xmlString = await wasm.exportIDS(doc as Record<string, unknown>) as string;
|
||||
|
||||
return xmlString;
|
||||
}
|
||||
|
||||
export async function exportDocument(docId) {
|
||||
export async function exportDocument(docId: string) {
|
||||
const doc = $state.snapshot(Module.documents[docId]);
|
||||
|
||||
// Validate
|
||||
@@ -162,37 +174,38 @@ export async function exportDocument(docId) {
|
||||
throw new Error("Please create at least one specification before exporting the document.");
|
||||
}
|
||||
|
||||
const xmlString = await wasm.exportIDS(doc);
|
||||
const xmlString = await wasm.exportIDS(doc as Record<string, unknown>) as string;
|
||||
|
||||
// Create and download file
|
||||
const blob = new Blob([xmlString], { type: 'application/xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${Module.documents[docId].info.title.replace(/[^a-zA-Z0-9]/g, '_')}.ids`;
|
||||
const title = Module.documents[docId].info.title || "untitled";
|
||||
a.download = `${title.replace(/[^a-zA-Z0-9]/g, '_')}.ids`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export async function createSpecification(docId) {
|
||||
const spec = await wasm.createSpecification();
|
||||
export async function createSpecification(docId: string) {
|
||||
const spec = await wasm.createSpecification() as Specification;
|
||||
|
||||
// Add specification to document
|
||||
Module.documents[docId].specifications.specification.push(spec);
|
||||
|
||||
// Set as active specification
|
||||
if (Module.activeDocument == docId) {
|
||||
if (Module.activeDocument === docId) {
|
||||
const state = Module.states[docId];
|
||||
state.activeSpecification = Module.documents[docId].specifications.specification.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSpecification(docId, specId) {
|
||||
export async function deleteSpecification(docId: string, specId: number) {
|
||||
Module.documents[docId].specifications.specification.splice(specId, 1);
|
||||
|
||||
if (Module.activeDocument == docId) {
|
||||
if (Module.activeDocument === docId) {
|
||||
const state = Module.states[docId];
|
||||
if (state.activeSpecification == specId) {
|
||||
if (state.activeSpecification === specId) {
|
||||
// We need to wait for the next tick here because of Svelte's internal shenanigans
|
||||
await tick();
|
||||
setDocumentState(docId, { activeSpecification: null });
|
||||
@@ -209,37 +222,56 @@ export async function deleteSpecification(docId, specId) {
|
||||
* clause: "applicability", "requirements"
|
||||
* facet: "entity", "attribute", "classification", "partOf", "property", "material"
|
||||
*/
|
||||
export async function createFacet(docId, specId, clause, facet) {
|
||||
let facetObj;
|
||||
if (facet == "entity") {
|
||||
facetObj = await wasm.createEntityFacet(clause, {});
|
||||
} else if (facet == "attribute") {
|
||||
facetObj = await wasm.createAttributeFacet(clause, {});
|
||||
} else if (facet == "classification") {
|
||||
facetObj = await wasm.createClassificationFacet(clause, {});
|
||||
} else if (facet == "partOf") {
|
||||
facetObj = await wasm.createPartOfFacet(clause, {});
|
||||
} else if (facet == "property") {
|
||||
facetObj = await wasm.createPropertyFacet(clause, {});
|
||||
} else if (facet == "material") {
|
||||
facetObj = await wasm.createMaterialFacet(clause, {});
|
||||
export async function createFacet(
|
||||
docId: string,
|
||||
specId: number,
|
||||
clause: "applicability" | "requirements",
|
||||
facet: "entity" | "attribute" | "classification" | "partOf" | "property" | "material"
|
||||
) {
|
||||
let facetObj: Facet | undefined;
|
||||
if (facet === "entity") {
|
||||
facetObj = await wasm.createEntityFacet(clause, {}) as Facet;
|
||||
} else if (facet === "attribute") {
|
||||
facetObj = await wasm.createAttributeFacet(clause, {}) as Facet;
|
||||
} else if (facet === "classification") {
|
||||
facetObj = await wasm.createClassificationFacet(clause, {}) as Facet;
|
||||
} else if (facet === "partOf") {
|
||||
facetObj = await wasm.createPartOfFacet(clause, {}) as Facet;
|
||||
} else if (facet === "property") {
|
||||
facetObj = await wasm.createPropertyFacet(clause, {}) as Facet;
|
||||
} else if (facet === "material") {
|
||||
facetObj = await wasm.createMaterialFacet(clause, {}) as Facet;
|
||||
}
|
||||
|
||||
if (!(facet in Module.documents[docId].specifications.specification[specId][clause])) {
|
||||
Module.documents[docId].specifications.specification[specId][clause][facet] = [];
|
||||
if (!facetObj) return;
|
||||
|
||||
const spec = Module.documents[docId].specifications.specification[specId];
|
||||
const clauseKey = clause as "applicability" | "requirements";
|
||||
if (!spec[clauseKey]) spec[clauseKey] = {};
|
||||
if (!(facet in (spec[clauseKey] as Record<string, unknown>))) {
|
||||
(spec[clauseKey] as Record<string, unknown>)[facet] = [];
|
||||
}
|
||||
|
||||
Module.documents[docId].specifications.specification[specId][clause][facet].push(facetObj);
|
||||
((spec[clauseKey] as Record<string, unknown>)[facet] as Facet[]).push(facetObj);
|
||||
}
|
||||
|
||||
export async function deleteFacet(docId, specId, clause, facet, facetId) {
|
||||
delete Module.documents[docId].specifications.specification[specId][clause][facet][facetId];
|
||||
export async function deleteFacet(
|
||||
docId: string,
|
||||
specId: number,
|
||||
clause: "applicability" | "requirements",
|
||||
facet: "entity" | "attribute" | "classification" | "partOf" | "property" | "material",
|
||||
facetId: number
|
||||
) {
|
||||
const spec = Module.documents[docId].specifications.specification[specId];
|
||||
const list = (spec[clause] as Record<string, unknown> | undefined)?.[facet] as Facet[] | undefined;
|
||||
if (!list) return;
|
||||
delete list[facetId];
|
||||
}
|
||||
|
||||
export function getSpecUsage(spec) {
|
||||
export function getSpecUsage(spec?: Specification | null): IdsCardinality {
|
||||
if (!spec?.applicability) return 'required';
|
||||
const minOccurs = spec.applicability["@minOccurs"];
|
||||
const maxOccurs = spec.applicability["@maxOccurs"];
|
||||
const minOccurs = spec.applicability["@minOccurs"] as number | undefined;
|
||||
const maxOccurs = spec.applicability["@maxOccurs"] as number | "unbounded" | undefined;
|
||||
|
||||
if (minOccurs === 1 && maxOccurs === "unbounded") return 'required';
|
||||
if (minOccurs === 0 && maxOccurs === "unbounded") return 'optional';
|
||||
@@ -248,85 +280,90 @@ export function getSpecUsage(spec) {
|
||||
};
|
||||
|
||||
// Converts facet to human-readable description
|
||||
export function stringifyFacet(clauseType, facet, facetType, spec) {
|
||||
export function stringifyFacet(
|
||||
clauseType: "applicability" | "requirements",
|
||||
facet: Facet,
|
||||
facetType: string,
|
||||
spec?: Specification | null
|
||||
) {
|
||||
if (!facet) return "";
|
||||
|
||||
const usage = getSpecUsage(spec);
|
||||
const descriptions = [];
|
||||
const descriptions: string[] = [];
|
||||
|
||||
// Entity facet
|
||||
if (facetType === "entity") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`All data where IFC class ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`All data where IFC class ${stringifyValue(facet.name as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be data where IFC class ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`Shall be data where IFC class ${stringifyValue(facet.name as FacetValue)}`);
|
||||
}
|
||||
|
||||
if (facet.predefinedType) {
|
||||
descriptions.push(`and type ${stringifyValue(facet.predefinedType)}`);
|
||||
descriptions.push(`and type ${stringifyValue(facet.predefinedType as FacetValue)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute facet
|
||||
else if (facetType === "attribute") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`All data where attribute ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`All data where attribute ${stringifyValue(facet.name as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be data where attribute ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`Shall be data where attribute ${stringifyValue(facet.name as FacetValue)}`);
|
||||
}
|
||||
descriptions.push(`and value ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`and value ${stringifyValue(facet.value as FacetValue)}`);
|
||||
}
|
||||
|
||||
// Property facet
|
||||
else if (facetType === "property") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`Elements where property ${stringifyValue(facet.baseName)}`);
|
||||
descriptions.push(`Elements where property ${stringifyValue(facet.baseName as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be elements where property ${stringifyValue(facet.baseName)}`);
|
||||
descriptions.push(`Shall be elements where property ${stringifyValue(facet.baseName as FacetValue)}`);
|
||||
}
|
||||
if (facet.value) {
|
||||
descriptions.push(`and value ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`and value ${stringifyValue(facet.value as FacetValue)}`);
|
||||
}
|
||||
descriptions.push(`and dataset ${stringifyValue(facet.propertySet)}`);
|
||||
descriptions.push(`and dataset ${stringifyValue(facet.propertySet as FacetValue)}`);
|
||||
}
|
||||
|
||||
// Classification facet
|
||||
else if (facetType === "classification") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`All data where classification system ${stringifyValue(facet.system)}`);
|
||||
descriptions.push(`All data where classification system ${stringifyValue(facet.system as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be data where classification system ${stringifyValue(facet.system)}`);
|
||||
descriptions.push(`Shall be data where classification system ${stringifyValue(facet.system as FacetValue)}`);
|
||||
}
|
||||
if (facet.value) {
|
||||
descriptions.push(`and classification ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`and classification ${stringifyValue(facet.value as FacetValue)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Material facet
|
||||
else if (facetType === "material") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`All data where material ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`All data where material ${stringifyValue(facet.value as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be data where material ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`Shall be data where material ${stringifyValue(facet.value as FacetValue)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// PartOf facet
|
||||
else if (facetType === "partOf") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`An element with an **${facet['@relation']}** relationship`);
|
||||
descriptions.push(`An element with an **${String(facet['@relation'] ?? "")}** relationship`);
|
||||
|
||||
if (facet.name) {
|
||||
descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name as FacetValue)}`);
|
||||
}
|
||||
} else {
|
||||
descriptions.push(`An element shall have an **${facet['@relation']}** relationship`);
|
||||
descriptions.push(`An element shall have an **${String(facet['@relation'] ?? "")}** relationship`);
|
||||
|
||||
if (facet.name) {
|
||||
descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name as FacetValue)}`);
|
||||
}
|
||||
if (facet.predefinedType) {
|
||||
descriptions.push(`and predefined type ${stringifyValue(facet.predefinedType)}`);
|
||||
descriptions.push(`and predefined type ${stringifyValue(facet.predefinedType as FacetValue)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,20 +373,22 @@ export function stringifyFacet(clauseType, facet, facetType, spec) {
|
||||
// Post-process for prohibited and optional requirements
|
||||
let isProhibited = false;
|
||||
|
||||
if (usage == "prohibited") isProhibited = !isProhibited;
|
||||
if (clauseType == "requirements" && "@cardinality" in facet && facet["@cardinality"] == "prohibited") isProhibited = !isProhibited;
|
||||
if (usage === "prohibited") isProhibited = !isProhibited;
|
||||
if (clauseType === "requirements" && "@cardinality" in facet && facet["@cardinality"] === "prohibited") {
|
||||
isProhibited = !isProhibited;
|
||||
}
|
||||
|
||||
if (isProhibited)
|
||||
combined = combined.replace("Shall", "Shall not").replace("shall", "shall not");
|
||||
|
||||
if (clauseType == "requirements" && "@cardinality" in facet && facet["@cardinality"] == "optional")
|
||||
if (clauseType === "requirements" && "@cardinality" in facet && facet["@cardinality"] === "optional")
|
||||
combined = combined.replace("Shall", "May").replace("shall", "may");
|
||||
|
||||
return renderFacetString(combined);
|
||||
}
|
||||
|
||||
// Converts value objects to human-readable strings
|
||||
function stringifyValue(value) {
|
||||
function stringifyValue(value?: FacetValue) {
|
||||
if (!value) return "is provided";
|
||||
if (value.simpleValue) return `is **${value.simpleValue}**`;
|
||||
if (value.restriction) return stringifyRestriction(value.restriction);
|
||||
@@ -357,7 +396,7 @@ function stringifyValue(value) {
|
||||
}
|
||||
|
||||
// Converts restriction objects to human-readable strings
|
||||
function stringifyRestriction(restriction) {
|
||||
function stringifyRestriction(restriction: Restriction) {
|
||||
if (!restriction) return "";
|
||||
|
||||
// Handle enumeration
|
||||
@@ -394,7 +433,7 @@ function stringifyRestriction(restriction) {
|
||||
if (restriction.maxExclusive && restriction.maxExclusive.length > 0) {
|
||||
parts.push(`**< ${restriction.maxExclusive[0]['@value'] || ''}**`);
|
||||
}
|
||||
return parts.length > 0 ? "is in range " + parts.join(", ") : "has range restriction";
|
||||
return parts.length > 0 ? `is in range ${parts.join(", ")}` : "has range restriction";
|
||||
}
|
||||
|
||||
// Handle length range restrictions
|
||||
@@ -406,18 +445,18 @@ function stringifyRestriction(restriction) {
|
||||
if (restriction.maxLength && restriction.maxLength.length > 0) {
|
||||
parts.push(`**max length ${restriction.maxLength[0]['@value'] || ''}**`);
|
||||
}
|
||||
return parts.length > 0 ? "has " + parts.join(", ") : "has length range restriction";
|
||||
return parts.length > 0 ? `has ${parts.join(", ")}` : "has length range restriction";
|
||||
}
|
||||
|
||||
return "has complex restriction";
|
||||
}
|
||||
|
||||
function renderFacetString(text) {
|
||||
function renderFacetString(text: string): string {
|
||||
// Convert **text** to <strong>text</strong>
|
||||
text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
const withStrong = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// Convert `text` to <code>text</code>
|
||||
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
const withCode = withStrong.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
|
||||
return text;
|
||||
}
|
||||
return withCode;
|
||||
}
|
||||
+14
-8
@@ -4,7 +4,7 @@ import { toast } from "svelte-sonner";
|
||||
* Show an error toast notification
|
||||
* @param {string} message - The error message to display
|
||||
*/
|
||||
export function error(message) {
|
||||
export function error(message: string): void {
|
||||
toast.error(message);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function error(message) {
|
||||
* Show a success toast notification
|
||||
* @param {string} message - The success message to display
|
||||
*/
|
||||
export function success(message) {
|
||||
export function success(message: string): void {
|
||||
toast.success(message);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function success(message) {
|
||||
* Show an info toast notification
|
||||
* @param {string} message - The info message to display
|
||||
*/
|
||||
export function info(message) {
|
||||
export function info(message: string): void {
|
||||
toast.info(message);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function info(message) {
|
||||
* Show a warning toast notification
|
||||
* @param {string} message - The warning message to display
|
||||
*/
|
||||
export function warning(message) {
|
||||
export function warning(message: string): void {
|
||||
toast.warning(message);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function warning(message) {
|
||||
* @param {string} message - The loading message to display
|
||||
* @returns {string} - Toast ID for dismissing later
|
||||
*/
|
||||
export function loading(message) {
|
||||
export function loading(message: string): string | number {
|
||||
return toast.loading(message);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export function loading(message) {
|
||||
* Dismiss a specific toast
|
||||
* @param {string} toastId - The toast ID to dismiss
|
||||
*/
|
||||
export function dismiss(toastId) {
|
||||
export function dismiss(toastId: string | number): void {
|
||||
toast.dismiss(toastId);
|
||||
}
|
||||
|
||||
@@ -57,10 +57,16 @@ export function dismiss(toastId) {
|
||||
* @param {string} messages.success - Success message
|
||||
* @param {string} messages.error - Error message
|
||||
*/
|
||||
export function promise(promiseToTrack, messages) {
|
||||
type PromiseToastMessages = {
|
||||
loading: string;
|
||||
success: string;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export function promise<T>(promiseToTrack: Promise<T>, messages: PromiseToastMessages) {
|
||||
return toast.promise(promiseToTrack, {
|
||||
loading: messages.loading,
|
||||
success: messages.success,
|
||||
error: messages.error,
|
||||
});
|
||||
}
|
||||
}
|
||||
+53
-42
@@ -5,6 +5,7 @@
|
||||
|
||||
import hyperid from "hyperid";
|
||||
import EventEmitter from "eventemitter3";
|
||||
import type { WorkerResponse } from "$src/types/wasm";
|
||||
|
||||
// Message types
|
||||
export const MessageType = {
|
||||
@@ -25,52 +26,57 @@ export const MessageType = {
|
||||
|
||||
// WASM module disposed
|
||||
DISPOSED: 'disposed'
|
||||
} as const;
|
||||
|
||||
type PendingMessage = {
|
||||
resolve: (value?: unknown) => void;
|
||||
reject: (reason?: unknown) => void;
|
||||
};
|
||||
|
||||
type WasmReadyState = boolean | Promise<boolean>;
|
||||
|
||||
class WASMModule extends EventEmitter {
|
||||
id = hyperid();
|
||||
ready = false;
|
||||
worker = null;
|
||||
pendingMessages = new Map();
|
||||
ready: WasmReadyState = false;
|
||||
worker: Worker | null = null;
|
||||
pendingMessages = new Map<string, PendingMessage>();
|
||||
|
||||
async init() {
|
||||
if (this.ready === true) return;
|
||||
else if (this.ready instanceof Promise) return this.ready;
|
||||
if (this.ready instanceof Promise) return this.ready;
|
||||
|
||||
this.worker = new Worker(new URL('./worker/worker.js', import.meta.url), {type: 'module'});
|
||||
this.worker = new Worker(new URL('./worker/worker.ts', import.meta.url), {type: 'module'});
|
||||
|
||||
this.worker.onmessage = (event) => {
|
||||
this.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {
|
||||
this._handleWorkerMessage(event.data);
|
||||
};
|
||||
|
||||
this.worker.onerror = (error) => {
|
||||
this.worker.onerror = (error: ErrorEvent) => {
|
||||
console.error('[WASM] Web worker error:', error);
|
||||
this._rejectPendingMessages(error);
|
||||
};
|
||||
|
||||
this.ready = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
await this._sendMessage(MessageType.INIT);
|
||||
resolve(true);
|
||||
} catch (error) {
|
||||
this.ready = this._sendMessage(MessageType.INIT)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
console.error('[WASM] Failed to initialize:', error);
|
||||
this.ready = false;
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
|
||||
return this.ready;
|
||||
}
|
||||
|
||||
async _sendMessage(type, payload = {}) {
|
||||
if (!this.worker) throw new Error('Worker not initialized');
|
||||
async _sendMessage(type: string, payload: Record<string, unknown> = {}): Promise<unknown> {
|
||||
const worker = this.worker;
|
||||
if (!worker) throw new Error('Worker not initialized');
|
||||
|
||||
const id = this.id();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pendingMessages.set(id, { resolve, reject });
|
||||
|
||||
this.worker.postMessage({
|
||||
worker.postMessage({
|
||||
type,
|
||||
payload,
|
||||
id
|
||||
@@ -78,7 +84,7 @@ class WASMModule extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
_handleWorkerMessage({ type, payload, id }) {
|
||||
_handleWorkerMessage({ type, payload, id }: WorkerResponse) {
|
||||
const pendingMessage = this.pendingMessages.get(id);
|
||||
|
||||
if (!pendingMessage) {
|
||||
@@ -97,23 +103,28 @@ class WASMModule extends EventEmitter {
|
||||
case MessageType.API_RESPONSE:
|
||||
resolve(payload);
|
||||
break;
|
||||
case MessageType.ERROR:
|
||||
reject(new Error(payload.message));
|
||||
case MessageType.ERROR: {
|
||||
const message =
|
||||
payload && typeof payload === "object" && "message" in payload
|
||||
? String(payload.message)
|
||||
: "Unknown worker error";
|
||||
reject(new Error(message));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn('[WASM] Unknown message type:', type);
|
||||
reject(new Error(`Unknown message type: ${type}`));
|
||||
}
|
||||
}
|
||||
|
||||
_rejectPendingMessages(error) {
|
||||
_rejectPendingMessages(error: unknown) {
|
||||
for (const { reject } of this.pendingMessages.values()) {
|
||||
reject(error);
|
||||
}
|
||||
this.pendingMessages.clear();
|
||||
}
|
||||
|
||||
async _apiCall(method, ...args) {
|
||||
async _apiCall(method: string, ...args: unknown[]) {
|
||||
if (!this.ready) await this.init();
|
||||
|
||||
const result = await this._sendMessage(MessageType.API_CALL, { method, args });
|
||||
@@ -123,35 +134,35 @@ class WASMModule extends EventEmitter {
|
||||
/**
|
||||
* Get all entity classes in a given IFC schema
|
||||
*/
|
||||
async getAllEntityClasses(schema) {
|
||||
async getAllEntityClasses(schema: string) {
|
||||
return this._apiCall('getAllEntityClasses', schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data types in a given IFC schema
|
||||
*/
|
||||
async getAllDataTypes(schema) {
|
||||
async getAllDataTypes(schema: string) {
|
||||
return this._apiCall('getAllDataTypes', schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get predefined types for a given IFC entity
|
||||
*/
|
||||
async getPredefinedTypes(schema, entity) {
|
||||
async getPredefinedTypes(schema: string, entity: string) {
|
||||
return this._apiCall('getPredefinedTypes', schema, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all attributes for a given IFC entity
|
||||
*/
|
||||
async getEntityAttributes(schema, entity) {
|
||||
async getEntityAttributes(schema: string, entity: string) {
|
||||
return this._apiCall('getEntityAttributes', schema, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get applicable property sets for a given IFC entity
|
||||
*/
|
||||
async getApplicablePsets(schema, entity, predefinedType = '') {
|
||||
async getApplicablePsets(schema: string, entity: string, predefinedType = '') {
|
||||
return this._apiCall('getApplicablePsets', schema, entity, predefinedType);
|
||||
}
|
||||
|
||||
@@ -172,21 +183,21 @@ class WASMModule extends EventEmitter {
|
||||
/**
|
||||
* Load an IFC file. Returns a unique ID for the loaded file.
|
||||
*/
|
||||
async loadIfc(ifcData) {
|
||||
async loadIfc(ifcData: number[] | Uint8Array | ArrayBuffer) {
|
||||
return this._apiCall('loadIfc', ifcData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload an IFC file
|
||||
*/
|
||||
async unloadIfc(ifcId) {
|
||||
async unloadIfc(ifcId: string) {
|
||||
return this._apiCall('unloadIfc', ifcId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit a loaded IFC file against IDS specifications
|
||||
*/
|
||||
async auditIfc(ifcId, idsData) {
|
||||
async auditIfc(ifcId: string, idsData: ArrayBuffer | Uint8Array | number[]) {
|
||||
const idsBytes = idsData instanceof ArrayBuffer ? new Uint8Array(idsData) : idsData;
|
||||
|
||||
return this._apiCall('auditIfc', ifcId, Array.from(idsBytes));
|
||||
@@ -204,70 +215,70 @@ class WASMModule extends EventEmitter {
|
||||
/**
|
||||
* Open an existing IDS from XML string
|
||||
*/
|
||||
async openIDS(idsXml, validate = false) {
|
||||
async openIDS(idsXml: string, validate = false) {
|
||||
return this._apiCall('openIDS', idsXml, validate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a specification
|
||||
*/
|
||||
async createSpecification(options = {}) {
|
||||
async createSpecification(options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createSpecification', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an entity facet
|
||||
*/
|
||||
async createEntityFacet(clause, options = {}) {
|
||||
async createEntityFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createEntityFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an attribute facet
|
||||
*/
|
||||
async createAttributeFacet(clause, options = {}) {
|
||||
async createAttributeFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createAttributeFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a property facet
|
||||
*/
|
||||
async createPropertyFacet(clause, options = {}) {
|
||||
async createPropertyFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createPropertyFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a material facet
|
||||
*/
|
||||
async createMaterialFacet(clause, options = {}) {
|
||||
async createMaterialFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createMaterialFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a classification facet
|
||||
*/
|
||||
async createClassificationFacet(clause, options = {}) {
|
||||
async createClassificationFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createClassificationFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a part-of facet
|
||||
*/
|
||||
async createPartOfFacet(clause, options = {}) {
|
||||
async createPartOfFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createPartOfFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an IDS object
|
||||
*/
|
||||
async validateIDS(idsObj) {
|
||||
async validateIDS(idsObj: Record<string, unknown>) {
|
||||
return await this._apiCall('validateIDS', idsObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export IDS instance to XML string
|
||||
*/
|
||||
async exportIDS(idsObj) {
|
||||
async exportIDS(idsObj: Record<string, unknown>) {
|
||||
return this._apiCall('exportIDS', idsObj);
|
||||
}
|
||||
|
||||
@@ -319,4 +330,4 @@ export const {
|
||||
dispose
|
||||
} = wasm;
|
||||
|
||||
export default wasm;
|
||||
export default wasm;
|
||||
+17
-16
@@ -1,12 +1,13 @@
|
||||
import config from '../../../config.json';
|
||||
import hyperid from 'hyperid';
|
||||
import type { AuditReportData } from "$src/types/report";
|
||||
|
||||
let pyodide = null;
|
||||
let id = hyperid();
|
||||
let pyodide: any = null;
|
||||
const id = hyperid();
|
||||
|
||||
let LoadedIFC = new Map();
|
||||
const LoadedIFC = new Map<string, unknown>();
|
||||
|
||||
export async function init(pdide) {
|
||||
export async function init(pdide: any) {
|
||||
pyodide = pdide;
|
||||
|
||||
// Load Python API bindings
|
||||
@@ -18,7 +19,7 @@ export async function init(pdide) {
|
||||
`);
|
||||
}
|
||||
|
||||
export async function getPredefinedTypes(schema, entity) {
|
||||
export async function getPredefinedTypes(schema: string, entity: string) {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_predefined_types_for_entity
|
||||
predef_types = get_predefined_types_for_entity("${schema}", "${entity}")
|
||||
@@ -27,7 +28,7 @@ export async function getPredefinedTypes(schema, entity) {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function getAllEntityClasses(schema) {
|
||||
export async function getAllEntityClasses(schema: string) {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_all_entity_classes
|
||||
entities = get_all_entity_classes("${schema}")
|
||||
@@ -36,7 +37,7 @@ export async function getAllEntityClasses(schema) {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function getAllDataTypes(schema) {
|
||||
export async function getAllDataTypes(schema: string) {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_all_data_types
|
||||
data_types = get_all_data_types("${schema}")
|
||||
@@ -45,7 +46,7 @@ export async function getAllDataTypes(schema) {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function getEntityAttributes(schema, entity) {
|
||||
export async function getEntityAttributes(schema: string, entity: string) {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_entity_attributes
|
||||
attrs = get_entity_attributes("${schema}", "${entity}")
|
||||
@@ -54,7 +55,7 @@ export async function getEntityAttributes(schema, entity) {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function getApplicablePsets(schema, entity, predefinedType = '') {
|
||||
export async function getApplicablePsets(schema: string, entity: string, predefinedType = '') {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_applicable_psets
|
||||
psets = get_applicable_psets("${schema}", "${entity}", "${predefinedType}")
|
||||
@@ -81,7 +82,7 @@ export async function getStandardClassificationSystems() {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function loadIfc(ifcData) {
|
||||
export async function loadIfc(ifcData: number[] | Uint8Array | ArrayBuffer) {
|
||||
const ifc_id = id();
|
||||
const path = `/tmp/${encodeURIComponent(ifc_id)}.ifc`;
|
||||
|
||||
@@ -97,14 +98,14 @@ export async function loadIfc(ifcData) {
|
||||
return ifc_id;
|
||||
}
|
||||
|
||||
export async function unloadIfc(ifcId) {
|
||||
export async function unloadIfc(ifcId: string) {
|
||||
const path = `/tmp/${encodeURIComponent(ifcId)}.ifc`;
|
||||
|
||||
pyodide.FS.unlink(path);
|
||||
LoadedIFC.delete(ifcId);
|
||||
}
|
||||
|
||||
export async function auditIfc(ifcId, idsData) {
|
||||
export async function auditIfc(ifcId: string, idsData: number[] | Uint8Array | ArrayBuffer) {
|
||||
const reporter = pyodide.pyimport("ifctester.reporter");
|
||||
const api = pyodide.pyimport("api");
|
||||
|
||||
@@ -116,16 +117,16 @@ export async function auditIfc(ifcId, idsData) {
|
||||
specs.validate(ifc);
|
||||
|
||||
// Create report in both HTML and JSON formats
|
||||
let jsonReporter = reporter.Json(specs);
|
||||
const jsonReporter = reporter.Json(specs);
|
||||
jsonReporter.report();
|
||||
const jsonReport = jsonReporter.to_string();
|
||||
|
||||
let htmlReporter = reporter.Html(specs);
|
||||
const htmlReporter = reporter.Html(specs);
|
||||
htmlReporter.report();
|
||||
const htmlReport = htmlReporter.to_string();
|
||||
|
||||
return {
|
||||
json: JSON.parse(jsonReport),
|
||||
json: JSON.parse(jsonReport) as AuditReportData,
|
||||
html: htmlReport
|
||||
};
|
||||
}
|
||||
@@ -142,4 +143,4 @@ export const API = {
|
||||
"loadIfc": loadIfc,
|
||||
"unloadIfc": unloadIfc,
|
||||
"auditIfc": auditIfc
|
||||
};
|
||||
};
|
||||
+98
-18
@@ -2,13 +2,19 @@
|
||||
* IDS module
|
||||
*/
|
||||
|
||||
let pyodide = null;
|
||||
let pyodide: any = null;
|
||||
|
||||
// IDS Python classes
|
||||
let Ids, Specification;
|
||||
let Entity, Attribute, Property, Material, Classification, PartOf;
|
||||
let Ids: any;
|
||||
let Specification: any;
|
||||
let Entity: any;
|
||||
let Attribute: any;
|
||||
let Property: any;
|
||||
let Material: any;
|
||||
let Classification: any;
|
||||
let PartOf: any;
|
||||
|
||||
export async function init(pdide) {
|
||||
export async function init(pdide: any) {
|
||||
pyodide = pdide;
|
||||
|
||||
await pyodide.loadPackagesFromImports(`
|
||||
@@ -29,24 +35,24 @@ export async function init(pdide) {
|
||||
PartOf = pyodide.pyimport("ifctester.facet").PartOf;
|
||||
}
|
||||
|
||||
function _idsToInstance(idsObj) {
|
||||
function _idsToInstance(idsObj: Record<string, unknown>) {
|
||||
const ids_raw = Ids();
|
||||
return ids_raw.parse(pyodide.toPy(idsObj))
|
||||
}
|
||||
|
||||
export function createIDS() {
|
||||
export function createIDS(): Record<string, unknown> {
|
||||
const ids_raw = Ids()
|
||||
return ids_raw.asdict().toJs({dict_converter: Object.fromEntries});
|
||||
}
|
||||
|
||||
export function openIDS(ids_xml, validate = false) {
|
||||
export function openIDS(ids_xml: string, validate = false): Record<string, unknown> {
|
||||
const ids_from_xml_string = pyodide.pyimport("api").ids_from_xml_string;
|
||||
const ids_raw = ids_from_xml_string(ids_xml, validate);
|
||||
|
||||
return ids_raw.asdict().toJs({dict_converter: Object.fromEntries});
|
||||
}
|
||||
|
||||
export function validateIDS(idsObj) {
|
||||
export function validateIDS(idsObj: Record<string, unknown>): boolean {
|
||||
const ids_raw = _idsToInstance(idsObj)
|
||||
const tempFilename = `temp_${Date.now()}.xml`;
|
||||
const isValid = ids_raw.to_xml(tempFilename); // to_xml validates the XML as well, as far as I understand
|
||||
@@ -60,12 +66,26 @@ export function validateIDS(idsObj) {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export function exportIDS(idsObj) {
|
||||
export function exportIDS(idsObj: Record<string, unknown>): string {
|
||||
const ids_raw = _idsToInstance(idsObj)
|
||||
return ids_raw.to_string();
|
||||
}
|
||||
|
||||
export function createSpecification({name = "Unnamed", ifcVersion = ["IFC2X3", "IFC4"], identifier = null, description = null, instructions = null, usage = "required"}) {
|
||||
export function createSpecification({
|
||||
name = "Unnamed",
|
||||
ifcVersion = ["IFC2X3", "IFC4"],
|
||||
identifier = null,
|
||||
description = null,
|
||||
instructions = null,
|
||||
usage = "required"
|
||||
}: {
|
||||
name?: string;
|
||||
ifcVersion?: string[];
|
||||
identifier?: string | null;
|
||||
description?: string | null;
|
||||
instructions?: string | null;
|
||||
usage?: string;
|
||||
} = {}): Record<string, unknown> {
|
||||
const spec = Specification.callKwargs({
|
||||
name: name,
|
||||
ifcVersion: ifcVersion,
|
||||
@@ -79,7 +99,14 @@ export function createSpecification({name = "Unnamed", ifcVersion = ["IFC2X3", "
|
||||
}
|
||||
|
||||
// @instructions
|
||||
export function createEntityFacet(clause, {name = "IFCWALL", predefinedType = null, instructions = null}) {
|
||||
export function createEntityFacet(
|
||||
clause: string,
|
||||
{name = "IFCWALL", predefinedType = null, instructions = null}: {
|
||||
name?: string;
|
||||
predefinedType?: string | null;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const entity = Entity.callKwargs({
|
||||
name: name,
|
||||
predefinedType: predefinedType,
|
||||
@@ -89,7 +116,15 @@ export function createEntityFacet(clause, {name = "IFCWALL", predefinedType = nu
|
||||
}
|
||||
|
||||
// @cardinality, @instructions
|
||||
export function createAttributeFacet(clause, {name = "Name", value = null, cardinality = "required", instructions = null}) {
|
||||
export function createAttributeFacet(
|
||||
clause: string,
|
||||
{name = "Name", value = null, cardinality = "required", instructions = null}: {
|
||||
name?: string;
|
||||
value?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const attribute = Attribute.callKwargs({
|
||||
name: name,
|
||||
value: value,
|
||||
@@ -100,7 +135,16 @@ export function createAttributeFacet(clause, {name = "Name", value = null, cardi
|
||||
}
|
||||
|
||||
// @uri, @cardinality, @instructions
|
||||
export function createClassificationFacet(clause, {value = null, system = null, uri = null, cardinality = "required", instructions = null}) {
|
||||
export function createClassificationFacet(
|
||||
clause: string,
|
||||
{value = null, system = null, uri = null, cardinality = "required", instructions = null}: {
|
||||
value?: string | null;
|
||||
system?: string | null;
|
||||
uri?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const classification = Classification.callKwargs({
|
||||
value: value,
|
||||
system: system,
|
||||
@@ -112,7 +156,16 @@ export function createClassificationFacet(clause, {value = null, system = null,
|
||||
}
|
||||
|
||||
// @relation, @cardinality, @instructions
|
||||
export function createPartOfFacet(clause, {name = "IFCWALL", predefinedType = null, relation = null, cardinality = "required", instructions = null}) {
|
||||
export function createPartOfFacet(
|
||||
clause: string,
|
||||
{name = "IFCWALL", predefinedType = null, relation = null, cardinality = "required", instructions = null}: {
|
||||
name?: string;
|
||||
predefinedType?: string | null;
|
||||
relation?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const part_of = PartOf.callKwargs({
|
||||
name: name,
|
||||
predefinedType: predefinedType,
|
||||
@@ -124,7 +177,26 @@ export function createPartOfFacet(clause, {name = "IFCWALL", predefinedType = nu
|
||||
}
|
||||
|
||||
// @dataType, @uri, @cardinality, @instructions
|
||||
export function createPropertyFacet(clause, {propertySet = "Property_Set", baseName = "propertyName", value = null, dataType = null, uri = null, cardinality = "required", instructions = null}) {
|
||||
export function createPropertyFacet(
|
||||
clause: string,
|
||||
{
|
||||
propertySet = "Property_Set",
|
||||
baseName = "propertyName",
|
||||
value = null,
|
||||
dataType = null,
|
||||
uri = null,
|
||||
cardinality = "required",
|
||||
instructions = null
|
||||
}: {
|
||||
propertySet?: string;
|
||||
baseName?: string;
|
||||
value?: string | null;
|
||||
dataType?: string | null;
|
||||
uri?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const property = Property.callKwargs({
|
||||
propertySet: propertySet,
|
||||
baseName: baseName,
|
||||
@@ -138,7 +210,15 @@ export function createPropertyFacet(clause, {propertySet = "Property_Set", baseN
|
||||
}
|
||||
|
||||
// @uri, @cardinality, @instructions
|
||||
export function createMaterialFacet(clause, {value = null, uri = null, cardinality = "required", instructions = null}) {
|
||||
export function createMaterialFacet(
|
||||
clause: string,
|
||||
{value = null, uri = null, cardinality = "required", instructions = null}: {
|
||||
value?: string | null;
|
||||
uri?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const material = Material.callKwargs({
|
||||
value: value,
|
||||
uri: uri,
|
||||
@@ -149,7 +229,7 @@ export function createMaterialFacet(clause, {value = null, uri = null, cardinali
|
||||
}
|
||||
|
||||
// Helper function to convert date to ISO format string
|
||||
export function formatDate(date) {
|
||||
export function formatDate(date?: string | number | Date | null): string | null {
|
||||
if (!date) return null;
|
||||
const d = new Date(date);
|
||||
return d.toISOString().split('T')[0];
|
||||
@@ -168,4 +248,4 @@ export const API = {
|
||||
"createPartOfFacet": createPartOfFacet,
|
||||
"createPropertyFacet": createPropertyFacet,
|
||||
"createMaterialFacet": createMaterialFacet,
|
||||
};
|
||||
};
|
||||
+21
-14
@@ -4,13 +4,14 @@
|
||||
|
||||
import { MessageType } from '../index';
|
||||
import config from '../../../config.json';
|
||||
import * as IDS from './ids.js';
|
||||
import * as IDS from './ids';
|
||||
import * as API from './api';
|
||||
import type { ApiCallPayload, WorkerRequest } from "$src/types/wasm";
|
||||
|
||||
let pyodide = null;
|
||||
let pyodide: any = null;
|
||||
let ready = false;
|
||||
|
||||
self.addEventListener('message', async (event) => {
|
||||
self.addEventListener('message', async (event: MessageEvent<WorkerRequest>) => {
|
||||
console.log("[worker] Received message:", event.data);
|
||||
const { type, payload, id } = event.data;
|
||||
|
||||
@@ -25,27 +26,33 @@ self.addEventListener('message', async (event) => {
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageType.API_CALL:
|
||||
case MessageType.API_CALL: {
|
||||
if (!ready) {
|
||||
throw new Error('[worker] Pyodide not initialized');
|
||||
}
|
||||
const result = await handleApiCall(payload);
|
||||
if (!payload) {
|
||||
throw new Error('[worker] Missing payload for API call');
|
||||
}
|
||||
const result = await handleApiCall(payload as ApiCallPayload);
|
||||
self.postMessage({
|
||||
type: MessageType.API_RESPONSE,
|
||||
payload: result,
|
||||
id
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`[worker] Unknown message type: ${type}`);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const stack = error instanceof Error ? error.stack : undefined;
|
||||
self.postMessage({
|
||||
type: MessageType.ERROR,
|
||||
payload: {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
message,
|
||||
stack
|
||||
},
|
||||
id
|
||||
});
|
||||
@@ -93,17 +100,17 @@ async function cleanupEnvironment() {
|
||||
console.log("[worker] Closed environment");
|
||||
}
|
||||
|
||||
async function handleApiCall({ method, args = [] }) {
|
||||
async function handleApiCall({ method, args = [] }: ApiCallPayload) {
|
||||
if (method === 'internal.cleanup') {
|
||||
await cleanupEnvironment();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (method in API.API) {
|
||||
return await API.API[method](...args);
|
||||
} else if (method in IDS.API) {
|
||||
return await IDS.API[method](...args);
|
||||
} else {
|
||||
throw new Error(`[worker] Unknown API method: ${method}`);
|
||||
return await (API.API as Record<string, (...params: unknown[]) => unknown>)[method](...args);
|
||||
}
|
||||
}
|
||||
if (method in IDS.API) {
|
||||
return await (IDS.API as Record<string, (...params: unknown[]) => unknown>)[method](...args);
|
||||
}
|
||||
throw new Error(`[worker] Unknown API method: ${method}`);
|
||||
}
|
||||
@@ -1,37 +1,56 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import FacetEditor from './FacetEditor.svelte';
|
||||
import CreateFacetDropdown from "$src/components/CreateFacetDropdown.svelte";
|
||||
import type { DocumentState, Facet, IdsDocument, Specification } from "$src/types/ids";
|
||||
|
||||
let { activeTab } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let activeSpecification = $derived(
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null
|
||||
);
|
||||
|
||||
async function addFacet (facetType) {
|
||||
if (!activeSpecification) return;
|
||||
async function addFacet(facetType: FacetType) {
|
||||
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||
|
||||
await IDS.createFacet(
|
||||
IDS.Module.activeDocument,
|
||||
documentState.activeSpecification,
|
||||
documentState.activeSpecification ?? 0,
|
||||
"applicability",
|
||||
facetType
|
||||
);
|
||||
}
|
||||
|
||||
async function removeFacet(facetType, facetIndex) {
|
||||
if (!activeSpecification) return;
|
||||
async function removeFacet(facetType: FacetType, facetIndex: number) {
|
||||
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||
|
||||
await IDS.deleteFacet(
|
||||
IDS.Module.activeDocument,
|
||||
documentState.activeSpecification,
|
||||
documentState.activeSpecification ?? 0,
|
||||
"applicability",
|
||||
facetType,
|
||||
facetIndex
|
||||
);
|
||||
}
|
||||
|
||||
let applicabilityEntries = $derived(
|
||||
activeSpecification?.applicability
|
||||
? (Object.entries(activeSpecification.applicability).filter(
|
||||
([facetType, facets]) =>
|
||||
facetType !== "@minOccurs" &&
|
||||
facetType !== "@maxOccurs" &&
|
||||
Array.isArray(facets)
|
||||
) as Array<[FacetType, Facet[]]>)
|
||||
: []
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="restrictions-panel">
|
||||
@@ -40,22 +59,19 @@
|
||||
<CreateFacetDropdown {addFacet} />
|
||||
</div>
|
||||
<div class="restrictions-list">
|
||||
{#if activeSpecification?.applicability}
|
||||
{#each Object.entries(activeSpecification.applicability) as [facetType, facets]}
|
||||
{#if facetType !== "@minOccurs" && facetType !== "@maxOccurs"}
|
||||
{#each facets as facet, index}
|
||||
<FacetEditor
|
||||
bind:facet={facets[index]}
|
||||
{facetType}
|
||||
specification={activeSpecification}
|
||||
activeTab="applicability"
|
||||
{removeFacet}
|
||||
{index}
|
||||
key={`${activeDocument}-${documentState?.activeSpecification}-applicability-${facetType}-${index}`}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if activeSpecification && applicabilityEntries.length > 0}
|
||||
{#each applicabilityEntries as [facetType, facets]}
|
||||
{#each facets as facet, index}
|
||||
<FacetEditor
|
||||
bind:facet={facets[index]}
|
||||
{facetType}
|
||||
specification={activeSpecification}
|
||||
activeTab="applicability"
|
||||
{removeFacet}
|
||||
{index}
|
||||
/>
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,40 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import RestrictionEditor from './RestrictionEditor.svelte';
|
||||
import {stringifyFacet} from "$src/modules/api/ids.svelte.js";
|
||||
import {stringifyFacet} from "$src/modules/api/ids.svelte";
|
||||
import type { Facet, Specification } from "$src/types/ids";
|
||||
|
||||
/**
|
||||
* Applicability facets wont have: "@uri", "@instructions", "@cardinality"
|
||||
* @ in name --> simple string value
|
||||
* else --> can be simpleValue, Restriction or list of Restrictions
|
||||
*/
|
||||
let { facet = $bindable(), facetType, activeTab, removeFacet, index, specification } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
const getSpecialProp = (prop) => {
|
||||
return facet[prop] ?? "";
|
||||
let {
|
||||
facet = $bindable<Facet>({}),
|
||||
facetType,
|
||||
activeTab,
|
||||
removeFacet,
|
||||
index,
|
||||
specification
|
||||
}: {
|
||||
facet: Facet;
|
||||
facetType: FacetType;
|
||||
activeTab: "applicability" | "requirements";
|
||||
removeFacet: (facetType: FacetType, facetIndex: number) => void | Promise<void>;
|
||||
index: number;
|
||||
specification: Specification;
|
||||
} = $props();
|
||||
|
||||
const getSpecialProp = (prop: string) => {
|
||||
const value = (facet as Record<string, unknown>)[prop];
|
||||
return typeof value === "string" ? value : "";
|
||||
};
|
||||
const setSpecialProp = (prop, value) => {
|
||||
facet[prop] = value;
|
||||
const setSpecialProp = (prop: string, value: string) => {
|
||||
(facet as Record<string, unknown>)[prop] = value;
|
||||
};
|
||||
|
||||
const baseId = `facet-${facetType}-${index}`;
|
||||
</script>
|
||||
|
||||
<div class="restriction-item">
|
||||
@@ -48,8 +68,8 @@
|
||||
<RestrictionEditor bind:facet={facet} fieldName="name" label="Entity Name" placeholder="e.g., IfcSpace" autocomplete="entityName" />
|
||||
<RestrictionEditor bind:facet={facet} fieldName="predefinedType" label="Predefined Type" placeholder="e.g., SOLIDWALL" autocomplete="predefinedType" />
|
||||
<div class="form-group">
|
||||
<label>Relation</label>
|
||||
<select class="form-input" bind:value={() => getSpecialProp("@relation"), (v) => setSpecialProp("@relation", v)}>
|
||||
<label for={`${baseId}-relation`}>Relation</label>
|
||||
<select class="form-input" id={`${baseId}-relation`} bind:value={() => getSpecialProp("@relation"), (v) => setSpecialProp("@relation", v)}>
|
||||
<option value="">Select relation...</option>
|
||||
<option value="IFCRELAGGREGATES">IFCRELAGGREGATES</option>
|
||||
<option value="IFCRELASSIGNSTOGROUP">IFCRELASSIGNSTOGROUP</option>
|
||||
@@ -62,8 +82,8 @@
|
||||
{#if activeTab === 'requirements'}
|
||||
{#if facetType !== 'entity'}
|
||||
<div class="form-group">
|
||||
<label>Cardinality</label>
|
||||
<select class="form-input" bind:value={() => getSpecialProp("@cardinality"), (v) => setSpecialProp("@cardinality", v)}>
|
||||
<label for={`${baseId}-cardinality`}>Cardinality</label>
|
||||
<select class="form-input" id={`${baseId}-cardinality`} bind:value={() => getSpecialProp("@cardinality"), (v) => setSpecialProp("@cardinality", v)}>
|
||||
<option value="required">Required</option>
|
||||
<option value="optional">Optional</option>
|
||||
<option value="prohibited">Prohibited</option>
|
||||
@@ -71,9 +91,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="form-group full-width">
|
||||
<label>Instructions</label>
|
||||
<textarea class="form-input" bind:value={() => getSpecialProp("@instructions"), (v) => setSpecialProp("@instructions", v)} placeholder="Optional instructions for IFC authors" rows="2"></textarea>
|
||||
<label for={`${baseId}-instructions`}>Instructions</label>
|
||||
<textarea class="form-input" id={`${baseId}-instructions`} bind:value={() => getSpecialProp("@instructions"), (v) => setSpecialProp("@instructions", v)} placeholder="Optional instructions for IFC authors" rows="2"></textarea>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import type { IdsDocument, IdsInfo } from "$src/types/ids";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
|
||||
const getProp = (prop) => {
|
||||
const getProp = (prop: keyof IdsInfo) => {
|
||||
return activeDocument?.info[prop] ?? "";
|
||||
};
|
||||
|
||||
const setProp = (prop, value) => {
|
||||
const setProp = (prop: keyof IdsInfo, value: string) => {
|
||||
if (!activeDocument) return;
|
||||
activeDocument.info[prop] = value;
|
||||
};
|
||||
</script>
|
||||
@@ -18,36 +22,36 @@
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>Title</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("title"), (v) => setProp("title", v)} placeholder="Enter IDS title">
|
||||
<label for="ids-title">Title</label>
|
||||
<input class="form-input" id="ids-title" type="text" bind:value={() => getProp("title"), (v) => setProp("title", v)} placeholder="Enter IDS title">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Author Email</label>
|
||||
<input class="form-input" type="email" bind:value={() => getProp("author"), (v) => setProp("author", v)} placeholder="Enter author">
|
||||
<label for="ids-author">Author Email</label>
|
||||
<input class="form-input" id="ids-author" type="email" bind:value={() => getProp("author"), (v) => setProp("author", v)} placeholder="Enter author">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Version</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("version"), (v) => setProp("version", v)} placeholder="Enter version">
|
||||
<label for="ids-version">Version</label>
|
||||
<input class="form-input" id="ids-version" type="text" bind:value={() => getProp("version"), (v) => setProp("version", v)} placeholder="Enter version">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Date</label>
|
||||
<input class="form-input" type="date" bind:value={() => getProp("date"), (v) => setProp("date", v)}>
|
||||
<label for="ids-date">Date</label>
|
||||
<input class="form-input" id="ids-date" type="date" bind:value={() => getProp("date"), (v) => setProp("date", v)}>
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label>Description</label>
|
||||
<textarea class="form-input" bind:value={() => getProp("description"), (v) => setProp("description", v)} placeholder="Enter description" rows="3"></textarea>
|
||||
<label for="ids-description">Description</label>
|
||||
<textarea class="form-input" id="ids-description" bind:value={() => getProp("description"), (v) => setProp("description", v)} placeholder="Enter description" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Purpose</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("purpose"), (v) => setProp("purpose", v)} placeholder="Enter purpose">
|
||||
<label for="ids-purpose">Purpose</label>
|
||||
<input class="form-input" id="ids-purpose" type="text" bind:value={() => getProp("purpose"), (v) => setProp("purpose", v)} placeholder="Enter purpose">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Milestone</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("milestone"), (v) => setProp("milestone", v)} placeholder="Enter milestone">
|
||||
<label for="ids-milestone">Milestone</label>
|
||||
<input class="form-input" id="ids-milestone" type="text" bind:value={() => getProp("milestone"), (v) => setProp("milestone", v)} placeholder="Enter milestone">
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label>Copyright</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("copyright"), (v) => setProp("copyright", v)} placeholder="Enter copyright">
|
||||
<label for="ids-copyright">Copyright</label>
|
||||
<input class="form-input" id="ids-copyright" type="text" bind:value={() => getProp("copyright"), (v) => setProp("copyright", v)} placeholder="Enter copyright">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import { getAuditReportById, downloadAuditReport } from "$src/modules/api/api.svelte.js";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import { getAuditReportById, downloadAuditReport } from "$src/modules/api/api.svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
import * as Tooltip from "$src/lib/components/ui/tooltip";
|
||||
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||
import type { DocumentState, IdsDocument } from "$src/types/ids";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let auditReport = $derived(documentState?.auditReport ? getAuditReportById(documentState.auditReport) : null);
|
||||
let expandedSpecs = $state(new Set());
|
||||
let expandedRequirements = $state(new Set());
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let auditReport = $derived(
|
||||
documentState?.auditReport ? (getAuditReportById(documentState.auditReport) as AuditReport | undefined) : null
|
||||
);
|
||||
let expandedSpecs = $state(new Set<number>());
|
||||
let expandedRequirements = $state(new Set<string>());
|
||||
let allExpanded = $state(false);
|
||||
|
||||
// Open Editor mode and jump to a specific specification
|
||||
function editSpecification(index) {
|
||||
function editSpecification(index: number) {
|
||||
if (IDS.Module.activeDocument) {
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, {
|
||||
viewMode: 'editor',
|
||||
@@ -23,13 +31,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSpecification(index) {
|
||||
function toggleSpecification(index: number) {
|
||||
if (expandedSpecs.has(index)) {
|
||||
expandedSpecs.delete(index);
|
||||
} else {
|
||||
expandedSpecs.add(index);
|
||||
}
|
||||
expandedSpecs = new Set(expandedSpecs);
|
||||
expandedSpecs = new Set<number>(expandedSpecs);
|
||||
}
|
||||
|
||||
function toggleAllSpecifications() {
|
||||
@@ -37,7 +45,7 @@
|
||||
|
||||
if (allExpanded) {
|
||||
// Collapse all
|
||||
expandedSpecs = new Set();
|
||||
expandedSpecs = new Set<number>();
|
||||
allExpanded = false;
|
||||
} else {
|
||||
// Expand all
|
||||
@@ -47,26 +55,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getSpecificationStatus(specIndex, auditData) {
|
||||
function getSpecificationStatus(specIndex: number, auditData: AuditReportData) {
|
||||
const spec = auditData.specifications[specIndex];
|
||||
if (!spec) return null;
|
||||
return spec.is_skipped ? 'skipped' : spec.status;
|
||||
}
|
||||
|
||||
function getSpecificationStats(specIndex, auditData) {
|
||||
function getSpecificationStats(specIndex: number, auditData: AuditReportData) {
|
||||
const spec = auditData.specifications[specIndex];
|
||||
if (!spec) return null;
|
||||
return {
|
||||
requirements: `${spec.total_requirements || 0}`,
|
||||
requirementsPassed: `${spec.total_requirements_pass || 0}`,
|
||||
checksTotal: `${spec.total_checks || 0}`,
|
||||
checksPassed: `${spec.total_checks_pass || 0}`,
|
||||
applicableTotal: `${spec.total_applicable || 0}`,
|
||||
applicablePassed: `${spec.total_applicable_pass || 0}`
|
||||
requirements: spec.total_requirements || 0,
|
||||
requirementsPassed: spec.total_requirements_pass || 0,
|
||||
checksTotal: spec.total_checks || 0,
|
||||
checksPassed: spec.total_checks_pass || 0,
|
||||
applicableTotal: spec.total_applicable || 0,
|
||||
applicablePassed: spec.total_applicable_pass || 0
|
||||
};
|
||||
}
|
||||
|
||||
function getSpecificationReason(specIndex, auditData) {
|
||||
function getSpecificationReason(specIndex: number, auditData: AuditReportData) {
|
||||
const spec = auditData.specifications[specIndex];
|
||||
if (!spec) return null;
|
||||
|
||||
@@ -97,29 +105,37 @@
|
||||
await downloadAuditReport(auditReport.id);
|
||||
success('Audit report downloaded successfully');
|
||||
} catch (err) {
|
||||
error(`Failed to download report: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to download report: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getRequirementStatus(specIndex, reqIndex, auditData) {
|
||||
function getRequirementStatus(specIndex: number, reqIndex: number, auditData: AuditReportData) {
|
||||
const spec = auditData.specifications[specIndex];
|
||||
if (!spec || !spec.requirements || !spec.requirements[reqIndex]) return null;
|
||||
return spec.requirements[reqIndex];
|
||||
}
|
||||
|
||||
function toggleRequirementDetails(specIndex, reqIndex) {
|
||||
function toggleRequirementDetails(specIndex: number, reqIndex: number) {
|
||||
const key = `${specIndex}-${reqIndex}`;
|
||||
if (expandedRequirements.has(key)) {
|
||||
expandedRequirements.delete(key);
|
||||
} else {
|
||||
expandedRequirements.add(key);
|
||||
}
|
||||
expandedRequirements = new Set(expandedRequirements);
|
||||
expandedRequirements = new Set<string>(expandedRequirements);
|
||||
}
|
||||
|
||||
function isRequirementDetailsExpanded(specIndex, reqIndex) {
|
||||
function isRequirementDetailsExpanded(specIndex: number, reqIndex: number) {
|
||||
return expandedRequirements.has(`${specIndex}-${reqIndex}`);
|
||||
}
|
||||
|
||||
const handleActivation = (event: KeyboardEvent, action: () => void) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
action();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="ids-viewer">
|
||||
@@ -208,7 +224,14 @@
|
||||
</div>
|
||||
{#each activeDocument.specifications.specification as spec, index}
|
||||
<div class="specification-card {auditReport ? 'with-audit' : ''} {auditReport && getSpecificationStatus(index, auditReport.data) !== null ? (getSpecificationStatus(index, auditReport.data) === 'skipped' ? 'spec-skipped' : (getSpecificationStatus(index, auditReport.data) ? 'spec-pass' : 'spec-fail')) : ''}">
|
||||
<div class="spec-card-header" onclick={() => toggleSpecification(index)}>
|
||||
<div
|
||||
class="spec-card-header"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-expanded={expandedSpecs.has(index)}
|
||||
onclick={() => toggleSpecification(index)}
|
||||
onkeydown={(event) => handleActivation(event, () => toggleSpecification(index))}
|
||||
>
|
||||
<div class="spec-title-section">
|
||||
<div class="spec-title-row">
|
||||
<h2>{spec["@name"] || `Specification ${index + 1}`}</h2>
|
||||
@@ -427,7 +450,7 @@
|
||||
{/if}
|
||||
{/if}
|
||||
</button>
|
||||
{#if isRequirementDetailsExpanded(index, facetIndex)}
|
||||
{#if reqAuditData && isRequirementDetailsExpanded(index, facetIndex)}
|
||||
<div class="facet-expansion">
|
||||
<div class="entity-tables">
|
||||
{#if reqAuditData.passed_entities && reqAuditData.passed_entities.length > 0}
|
||||
|
||||
@@ -1,37 +1,53 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import FacetEditor from './FacetEditor.svelte';
|
||||
import CreateFacetDropdown from "$src/components/CreateFacetDropdown.svelte";
|
||||
import type { DocumentState, Facet, IdsDocument, Specification } from "$src/types/ids";
|
||||
|
||||
let { activeTab } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let activeSpecification = $derived(
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null
|
||||
);
|
||||
|
||||
async function addFacet (facetType) {
|
||||
if (!activeSpecification) return;
|
||||
async function addFacet(facetType: FacetType) {
|
||||
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||
|
||||
await IDS.createFacet(
|
||||
IDS.Module.activeDocument,
|
||||
documentState.activeSpecification,
|
||||
documentState.activeSpecification ?? 0,
|
||||
"requirements",
|
||||
facetType
|
||||
);
|
||||
}
|
||||
|
||||
async function removeFacet(facetType, facetIndex) {
|
||||
if (!activeSpecification) return;
|
||||
async function removeFacet(facetType: FacetType, facetIndex: number) {
|
||||
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||
|
||||
await IDS.deleteFacet(
|
||||
IDS.Module.activeDocument,
|
||||
documentState.activeSpecification,
|
||||
documentState.activeSpecification ?? 0,
|
||||
"requirements",
|
||||
facetType,
|
||||
facetIndex
|
||||
);
|
||||
}
|
||||
|
||||
let requirementEntries = $derived(
|
||||
activeSpecification?.requirements
|
||||
? (Object.entries(activeSpecification.requirements).filter(([, facets]) =>
|
||||
Array.isArray(facets)
|
||||
) as Array<[FacetType, Facet[]]>)
|
||||
: []
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="restrictions-panel">
|
||||
@@ -40,8 +56,8 @@
|
||||
<CreateFacetDropdown {addFacet} />
|
||||
</div>
|
||||
<div class="restrictions-list">
|
||||
{#if activeSpecification?.requirements}
|
||||
{#each Object.entries(activeSpecification.requirements) as [facetType, facets]}
|
||||
{#if activeSpecification && requirementEntries.length > 0}
|
||||
{#each requirementEntries as [facetType, facets]}
|
||||
{#each facets as facet, index}
|
||||
<FacetEditor
|
||||
bind:facet={facets[index]}
|
||||
@@ -50,10 +66,9 @@
|
||||
activeTab="requirements"
|
||||
{removeFacet}
|
||||
{index}
|
||||
key={`${activeDocument}-${documentState?.activeSpecification}-requirements-${facetType}-${index}`}
|
||||
/>
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Svelecte from 'svelecte';
|
||||
import { getEntityClasses, getMaterialCategories, getClassificationSystems, getDataTypes, getPredefinedTypes, getEntityAttributes, getApplicablePsets } from '$src/modules/api/api.svelte.js';
|
||||
import * as IDS from '$src/modules/api/ids.svelte.js';
|
||||
import { getEntityClasses, getMaterialCategories, getClassificationSystems, getDataTypes, getPredefinedTypes, getEntityAttributes, getApplicablePsets } from '$src/modules/api/api.svelte';
|
||||
import * as IDS from '$src/modules/api/ids.svelte';
|
||||
import type { DocumentState, Facet, FacetValue, IdsDocument, Restriction, RestrictionValue, Specification } from '$src/types/ids';
|
||||
|
||||
let { facet = $bindable(), fieldName, label, placeholder, autocomplete = null, isSpecialProp = false } = $props();
|
||||
type AutocompleteType =
|
||||
| 'entityName'
|
||||
| 'material'
|
||||
| 'classificationSystem'
|
||||
| 'predefinedType'
|
||||
| 'attributeName'
|
||||
| 'propertySet'
|
||||
| 'dataType'
|
||||
| null;
|
||||
|
||||
let {
|
||||
facet = $bindable<Facet>({}),
|
||||
fieldName,
|
||||
label,
|
||||
placeholder,
|
||||
autocomplete = null,
|
||||
isSpecialProp = false
|
||||
}: {
|
||||
facet: Facet;
|
||||
fieldName: string;
|
||||
label: string;
|
||||
placeholder: string;
|
||||
autocomplete?: AutocompleteType;
|
||||
isSpecialProp?: boolean;
|
||||
} = $props();
|
||||
|
||||
const isEntityNameField = autocomplete === 'entityName';
|
||||
const isMaterialField = autocomplete === 'material';
|
||||
@@ -12,22 +37,30 @@
|
||||
const isAttributeNameField = autocomplete === 'attributeName';
|
||||
const isPropertySetField = autocomplete === 'propertySet';
|
||||
const isDataTypeField = autocomplete === 'dataType';
|
||||
|
||||
const baseId = `restriction-${fieldName}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
|
||||
// Predefined Types autocompletions
|
||||
let predefinedTypeOptions = $state([]);
|
||||
let predefinedTypeOptions: string[] = $state([]);
|
||||
|
||||
// Attribute Names autocompletions
|
||||
let attributeNameOptions = $state([]);
|
||||
let attributeNameOptions: string[] = $state([]);
|
||||
|
||||
// Property Sets autocompletions
|
||||
let propertySetOptions = $state([]);
|
||||
let propertySetOptions: string[] = $state([]);
|
||||
|
||||
// Get the active specification's IFC schemas
|
||||
const getIfcVersions = () => {
|
||||
const activeDocument = IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null;
|
||||
const documentState = IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null;
|
||||
const activeSpecification = activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null;
|
||||
const activeDocument = IDS.Module.activeDocument
|
||||
? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument)
|
||||
: null;
|
||||
const documentState = IDS.Module.activeDocument
|
||||
? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState)
|
||||
: null;
|
||||
const activeSpecification =
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null;
|
||||
|
||||
const versions = activeSpecification?.["@ifcVersion"] || ['IFC4'];
|
||||
// TODO: Fix: Filter out IFC4X3 because it's buggy
|
||||
@@ -36,7 +69,7 @@
|
||||
|
||||
// Get the entity name from facet
|
||||
const getEntityName = () => {
|
||||
const nameField = facet?.name;
|
||||
const nameField = (facet as Record<string, unknown>)?.name as FacetValue | undefined;
|
||||
if (!nameField) return '';
|
||||
if (nameField.simpleValue) return nameField.simpleValue;
|
||||
return '';
|
||||
@@ -44,18 +77,24 @@
|
||||
|
||||
// Get all entity names from Entity facets in Applicability
|
||||
const getApplicabilityEntityNames = () => {
|
||||
const activeDocument = IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null;
|
||||
const documentState = IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null;
|
||||
const activeSpecification = activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null;
|
||||
const activeDocument = IDS.Module.activeDocument
|
||||
? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument)
|
||||
: null;
|
||||
const documentState = IDS.Module.activeDocument
|
||||
? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState)
|
||||
: null;
|
||||
const activeSpecification =
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null;
|
||||
|
||||
if (!activeSpecification?.applicability?.entity) return [];
|
||||
|
||||
const entityFacets = activeSpecification.applicability.entity;
|
||||
const entityNames = [];
|
||||
const entityFacets = activeSpecification.applicability.entity as Facet[];
|
||||
const entityNames: string[] = [];
|
||||
|
||||
entityFacets.forEach(entityFacet => {
|
||||
const nameField = entityFacet.name;
|
||||
const nameField = (entityFacet as Record<string, unknown>).name as FacetValue | undefined;
|
||||
if (!nameField) return;
|
||||
|
||||
if (nameField.simpleValue) {
|
||||
@@ -74,19 +113,25 @@
|
||||
|
||||
// Get entity facets with their predefined types
|
||||
const getApplicabilityEntityFacets = () => {
|
||||
const activeDocument = IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null;
|
||||
const documentState = IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null;
|
||||
const activeSpecification = activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null;
|
||||
const activeDocument = IDS.Module.activeDocument
|
||||
? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument)
|
||||
: null;
|
||||
const documentState = IDS.Module.activeDocument
|
||||
? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState)
|
||||
: null;
|
||||
const activeSpecification =
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null;
|
||||
|
||||
if (!activeSpecification?.applicability?.entity) return [];
|
||||
|
||||
return activeSpecification.applicability.entity.map(entityFacet => {
|
||||
const nameField = entityFacet.name;
|
||||
const predefinedTypeField = entityFacet.predefinedType;
|
||||
return (activeSpecification.applicability.entity as Facet[]).map(entityFacet => {
|
||||
const nameField = (entityFacet as Record<string, unknown>).name as FacetValue | undefined;
|
||||
const predefinedTypeField = (entityFacet as Record<string, unknown>).predefinedType as FacetValue | undefined;
|
||||
|
||||
// Extract entity name
|
||||
let entityNames = [];
|
||||
let entityNames: string[] = [];
|
||||
if (nameField?.simpleValue) {
|
||||
entityNames = [nameField.simpleValue];
|
||||
} else if (nameField?.restriction?.enumeration) {
|
||||
@@ -99,9 +144,12 @@
|
||||
let predefinedType = '';
|
||||
if (predefinedTypeField?.simpleValue) {
|
||||
predefinedType = predefinedTypeField.simpleValue;
|
||||
} else if (predefinedTypeField?.restriction?.enumeration?.length > 0) {
|
||||
// For enumeration predefined types, use the first one or empty string
|
||||
predefinedType = predefinedTypeField.restriction.enumeration[0]['@value'] || '';
|
||||
} else {
|
||||
const enumValues = predefinedTypeField?.restriction?.enumeration;
|
||||
if (enumValues && enumValues.length > 0) {
|
||||
// For enumeration predefined types, use the first one or empty string
|
||||
predefinedType = enumValues[0]['@value'] || '';
|
||||
}
|
||||
}
|
||||
|
||||
return { entityNames, predefinedType };
|
||||
@@ -109,7 +157,8 @@
|
||||
};
|
||||
|
||||
// Contextual autocompletions
|
||||
$effect(async () => {
|
||||
$effect(() => {
|
||||
void (async () => {
|
||||
|
||||
// Predefined Types
|
||||
if (isPredefinedTypeField) {
|
||||
@@ -122,10 +171,10 @@
|
||||
const typePromises = ifcVersions.map(schema =>
|
||||
getPredefinedTypes(schema, entityName.toUpperCase())
|
||||
);
|
||||
const typeSets = await Promise.all(typePromises);
|
||||
const typeSets = await Promise.all(typePromises) as string[][];
|
||||
|
||||
// Deduplicate predefined types across all schemas
|
||||
const allTypes = new Set();
|
||||
const allTypes = new Set<string>();
|
||||
typeSets.forEach(types => {
|
||||
if (types && Array.isArray(types)) {
|
||||
types.forEach(type => allTypes.add(type));
|
||||
@@ -150,19 +199,19 @@
|
||||
if (entityNames.length > 0 && ifcVersions.length > 0) {
|
||||
try {
|
||||
// Fetch attributes for all entity names across all selected schemas
|
||||
const attributePromises = [];
|
||||
const attributePromises: Array<Promise<{ name: string }[]>> = [];
|
||||
entityNames.forEach(entityName => {
|
||||
ifcVersions.forEach(schema => {
|
||||
attributePromises.push(
|
||||
getEntityAttributes(schema, entityName.toUpperCase())
|
||||
getEntityAttributes(schema, entityName.toUpperCase()) as Promise<{ name: string }[]>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const attributeSets = await Promise.all(attributePromises);
|
||||
const attributeSets = await Promise.all(attributePromises) as { name: string }[][];
|
||||
|
||||
// Deduplicate attribute names across all entities and schemas
|
||||
const allAttributes = new Set();
|
||||
const allAttributes = new Set<string>();
|
||||
attributeSets.forEach(attributes => {
|
||||
if (attributes && Array.isArray(attributes)) {
|
||||
attributes.forEach(attr => {
|
||||
@@ -191,21 +240,21 @@
|
||||
if (entityFacets.length > 0 && ifcVersions.length > 0) {
|
||||
try {
|
||||
// Fetch applicable property sets for all entity facets across all selected schemas
|
||||
const psetPromises = [];
|
||||
const psetPromises: Array<Promise<string[]>> = [];
|
||||
entityFacets.forEach(facet => {
|
||||
facet.entityNames.forEach(entityName => {
|
||||
ifcVersions.forEach(schema => {
|
||||
psetPromises.push(
|
||||
getApplicablePsets(schema, entityName.toUpperCase(), facet.predefinedType)
|
||||
getApplicablePsets(schema, entityName.toUpperCase(), facet.predefinedType) as Promise<string[]>
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const psetSets = await Promise.all(psetPromises);
|
||||
const psetSets = await Promise.all(psetPromises) as string[][];
|
||||
|
||||
// Deduplicate property set names
|
||||
const allPsets = new Set();
|
||||
const allPsets = new Set<string>();
|
||||
psetSets.forEach(psets => {
|
||||
if (psets && Array.isArray(psets)) {
|
||||
psets.forEach(pset => allPsets.add(pset));
|
||||
@@ -221,10 +270,11 @@
|
||||
propertySetOptions = [];
|
||||
}
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
// Get autocomplete options based on field type
|
||||
const getAutocompleteOptions = () => {
|
||||
const getAutocompleteOptions = (): string[] => {
|
||||
if (isEntityNameField) return getEntityClasses();
|
||||
if (isMaterialField) return getMaterialCategories();
|
||||
if (isClassificationSystemField) return Object.keys(getClassificationSystems());
|
||||
@@ -236,11 +286,11 @@
|
||||
};
|
||||
|
||||
const getRestrictionType = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
if (!fieldValue) return 'Simple';
|
||||
if (fieldValue.simpleValue !== undefined) return 'Simple';
|
||||
if (fieldValue['restriction']) {
|
||||
const restriction = fieldValue['restriction'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
if (restriction['enumeration']) return 'Enumeration';
|
||||
if (restriction['pattern']) return 'Pattern';
|
||||
if (restriction['minInclusive'] || restriction['maxInclusive'] ||
|
||||
@@ -251,39 +301,40 @@
|
||||
return 'Simple';
|
||||
};
|
||||
|
||||
const getSimpleValue = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const getSimpleValue = (): string => {
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | string | undefined;
|
||||
|
||||
// For special properties (eg. @dataType), we return the value directly
|
||||
if (isSpecialProp) return fieldValue || null;
|
||||
if (typeof fieldValue === 'string') return fieldValue;
|
||||
if (isSpecialProp) return '';
|
||||
|
||||
if (!fieldValue) return null;
|
||||
if (!fieldValue) return '';
|
||||
if (fieldValue.simpleValue !== undefined) return fieldValue.simpleValue;
|
||||
return null;
|
||||
return '';
|
||||
};
|
||||
|
||||
const getEnumerationValues = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
if (!fieldValue?.['restriction']) return [''];
|
||||
const restriction = fieldValue['restriction'];
|
||||
const enumValues = restriction['enumeration'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const enumValues = restriction['enumeration'] as RestrictionValue[] | undefined;
|
||||
if (!enumValues) return [''];
|
||||
return enumValues.map(item => item['@value'] || '');
|
||||
};
|
||||
|
||||
const getPatternValue = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
if (!fieldValue?.['restriction']) return '';
|
||||
const restriction = fieldValue['restriction'];
|
||||
const pattern = restriction['pattern'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const pattern = restriction['pattern'] as RestrictionValue[] | undefined;
|
||||
if (!pattern || !pattern.length) return '';
|
||||
return pattern[0]['@value'] || '';
|
||||
};
|
||||
|
||||
const getRangeValues = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
if (!fieldValue?.['restriction']) return { min: '', max: '', minType: 'Inclusive', maxType: 'Inclusive' };
|
||||
const restriction = fieldValue['restriction'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
|
||||
let min = '', max = '', minType = 'Inclusive', maxType = 'Inclusive';
|
||||
|
||||
@@ -307,18 +358,18 @@
|
||||
};
|
||||
|
||||
const getLengthValue = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
if (!fieldValue?.['restriction']) return '';
|
||||
const restriction = fieldValue['restriction'];
|
||||
const length = restriction['length'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const length = restriction['length'] as RestrictionValue[] | undefined;
|
||||
if (!length || !length.length) return '';
|
||||
return length[0]['@value'] || '';
|
||||
};
|
||||
|
||||
const getLengthRangeValues = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
if (!fieldValue?.['restriction']) return { min: '', max: '' };
|
||||
const restriction = fieldValue['restriction'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
|
||||
let min = '', max = '';
|
||||
|
||||
@@ -332,20 +383,18 @@
|
||||
return { min, max };
|
||||
};
|
||||
|
||||
const setSimpleValue = (value) => {
|
||||
const setSimpleValue = (value: string) => {
|
||||
// For special properties (eg. @dataType), we set the value directly
|
||||
if (isSpecialProp) {
|
||||
facet[fieldName] = value;
|
||||
(facet as Record<string, unknown>)[fieldName] = value;
|
||||
return;
|
||||
}
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
facet[fieldName] = { simpleValue: value };
|
||||
(facet as Record<string, unknown>)[fieldName] = { simpleValue: value };
|
||||
};
|
||||
|
||||
const setEnumerationValues = (values) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
const setEnumerationValues = (values: string[]) => {
|
||||
const enumItems = values.filter(v => v && typeof v === 'string' && v.trim() !== '').map(v => ({ '@value': v }));
|
||||
facet[fieldName] = {
|
||||
(facet as Record<string, unknown>)[fieldName] = {
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'enumeration': enumItems
|
||||
@@ -353,9 +402,8 @@
|
||||
};
|
||||
};
|
||||
|
||||
const setPatternValue = (value) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
facet[fieldName] = {
|
||||
const setPatternValue = (value: string) => {
|
||||
(facet as Record<string, unknown>)[fieldName] = {
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'pattern': [{ '@value': value }]
|
||||
@@ -363,25 +411,23 @@
|
||||
};
|
||||
};
|
||||
|
||||
const setRangeValues = (min, max, minType, maxType) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
const restriction = { '@base': 'xs:string' };
|
||||
const setRangeValues = (min: string, max: string, minType: string, maxType: string) => {
|
||||
const restriction: Restriction = { '@base': 'xs:string' };
|
||||
|
||||
if (min !== '') {
|
||||
const minKey = minType === 'Inclusive' ? 'minInclusive' : 'minExclusive';
|
||||
restriction[minKey] = [{ '@value': min }];
|
||||
(restriction as Record<string, RestrictionValue[]>)[minKey] = [{ '@value': min }];
|
||||
}
|
||||
if (max !== '') {
|
||||
const maxKey = maxType === 'Inclusive' ? 'maxInclusive' : 'maxExclusive';
|
||||
restriction[maxKey] = [{ '@value': max }];
|
||||
(restriction as Record<string, RestrictionValue[]>)[maxKey] = [{ '@value': max }];
|
||||
}
|
||||
|
||||
facet[fieldName] = { 'restriction': restriction };
|
||||
(facet as Record<string, unknown>)[fieldName] = { 'restriction': restriction };
|
||||
};
|
||||
|
||||
const setLengthValue = (value) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
facet[fieldName] = {
|
||||
const setLengthValue = (value: string) => {
|
||||
(facet as Record<string, unknown>)[fieldName] = {
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'length': [{ '@value': value }]
|
||||
@@ -389,20 +435,19 @@
|
||||
};
|
||||
};
|
||||
|
||||
const setLengthRangeValues = (min, max) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
const restriction = { '@base': 'xs:string' };
|
||||
const setLengthRangeValues = (min: string, max: string) => {
|
||||
const restriction: Restriction = { '@base': 'xs:string' };
|
||||
|
||||
if (min !== '') restriction['minLength'] = [{ '@value': min }];
|
||||
if (max !== '') restriction['maxLength'] = [{ '@value': max }];
|
||||
|
||||
facet[fieldName] = { 'restriction': restriction };
|
||||
(facet as Record<string, unknown>)[fieldName] = { 'restriction': restriction };
|
||||
};
|
||||
|
||||
let restrictionType = $derived(getRestrictionType());
|
||||
let enumerationValues = $derived(getEnumerationValues());
|
||||
|
||||
const handleTypeChange = (newType) => {
|
||||
const handleTypeChange = (newType: string) => {
|
||||
restrictionType = newType;
|
||||
|
||||
switch (newType) {
|
||||
@@ -435,23 +480,27 @@
|
||||
enumerationValues = [...enumerationValues, ''];
|
||||
};
|
||||
|
||||
const removeEnumerationValue = (index) => {
|
||||
const removeEnumerationValue = (index: number) => {
|
||||
enumerationValues = enumerationValues.filter((_, i) => i !== index);
|
||||
setEnumerationValues(enumerationValues);
|
||||
};
|
||||
|
||||
const updateEnumerationValue = (index, value) => {
|
||||
const updateEnumerationValue = (index: number, value: string) => {
|
||||
enumerationValues[index] = value;
|
||||
setEnumerationValues(enumerationValues);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{label}</label>
|
||||
<div class="restriction-controls">
|
||||
<span class="form-label" id={`${baseId}-label`}>{label}</span>
|
||||
<div class="restriction-controls" role="group" aria-labelledby={`${baseId}-label`}>
|
||||
{#if !isSpecialProp}
|
||||
<div class="restriction-type-selector">
|
||||
<select class="form-input" bind:value={restrictionType} onchange={(e) => handleTypeChange(e.target.value)}>
|
||||
<select
|
||||
class="form-input"
|
||||
bind:value={restrictionType}
|
||||
onchange={(e) => handleTypeChange((e.target as HTMLSelectElement).value)}
|
||||
>
|
||||
<option value="Simple">Simple</option>
|
||||
<option value="Enumeration">Enumeration</option>
|
||||
<option value="Pattern">Pattern</option>
|
||||
@@ -477,7 +526,7 @@
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{:else}
|
||||
<input class="form-input" type="text" bind:value={() => getSimpleValue(), (v) => setSimpleValue(v)} {placeholder}>
|
||||
<input class="form-input" type="text" bind:value={() => getSimpleValue(), (v) => setSimpleValue(v)} {placeholder} aria-label={label}>
|
||||
{/if}
|
||||
|
||||
{:else if restrictionType === 'Enumeration'}
|
||||
@@ -497,7 +546,14 @@
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{:else}
|
||||
<input class="form-input" type="text" value={value} oninput={(e) => updateEnumerationValue(index, e.target.value)} {placeholder}>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
value={value}
|
||||
oninput={(e) => updateEnumerationValue(index, (e.target as HTMLInputElement).value)}
|
||||
{placeholder}
|
||||
aria-label={`${label} option ${index + 1}`}
|
||||
>
|
||||
{/if}
|
||||
<button class="btn-delete" onclick={() => removeEnumerationValue(index)} type="button" aria-label="Remove enumeration value">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -515,22 +571,42 @@
|
||||
</div>
|
||||
|
||||
{:else if restrictionType === 'Pattern'}
|
||||
<input class="form-input" type="text" bind:value={() => getPatternValue(), (v) => setPatternValue(v)} placeholder="Enter regex pattern (e.g., DT[0-9]{2})">
|
||||
<input class="form-input" type="text" bind:value={() => getPatternValue(), (v) => setPatternValue(v)} placeholder="Enter regex pattern (e.g., DT[0-9]{2})" aria-label={`${label} pattern`}>
|
||||
|
||||
{:else if restrictionType === 'Range'}
|
||||
<div class="range-controls">
|
||||
<div class="range-group">
|
||||
<label>Min</label>
|
||||
<input class="form-input" type="text" bind:value={() => getRangeValues().min, (v) => { const range = getRangeValues(); setRangeValues(v, range.max, range.minType, range.maxType); }} placeholder="0">
|
||||
<select class="form-input" bind:value={() => getRangeValues().minType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, v, range.maxType); }}>
|
||||
<label for={`${baseId}-range-min`}>Min</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
id={`${baseId}-range-min`}
|
||||
bind:value={() => getRangeValues().min, (v) => { const range = getRangeValues(); setRangeValues(v, range.max, range.minType, range.maxType); }}
|
||||
placeholder="0"
|
||||
>
|
||||
<select
|
||||
class="form-input"
|
||||
aria-label="Min bound type"
|
||||
bind:value={() => getRangeValues().minType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, v, range.maxType); }}
|
||||
>
|
||||
<option value="Inclusive">Inclusive</option>
|
||||
<option value="Exclusive">Exclusive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="range-group">
|
||||
<label>Max</label>
|
||||
<input class="form-input" type="text" bind:value={() => getRangeValues().max, (v) => { const range = getRangeValues(); setRangeValues(range.min, v, range.minType, range.maxType); }} placeholder="0">
|
||||
<select class="form-input" bind:value={() => getRangeValues().maxType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, range.minType, v); }}>
|
||||
<label for={`${baseId}-range-max`}>Max</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
id={`${baseId}-range-max`}
|
||||
bind:value={() => getRangeValues().max, (v) => { const range = getRangeValues(); setRangeValues(range.min, v, range.minType, range.maxType); }}
|
||||
placeholder="0"
|
||||
>
|
||||
<select
|
||||
class="form-input"
|
||||
aria-label="Max bound type"
|
||||
bind:value={() => getRangeValues().maxType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, range.minType, v); }}
|
||||
>
|
||||
<option value="Inclusive">Inclusive</option>
|
||||
<option value="Exclusive">Exclusive</option>
|
||||
</select>
|
||||
@@ -538,17 +614,17 @@
|
||||
</div>
|
||||
|
||||
{:else if restrictionType === 'Length'}
|
||||
<input class="form-input" type="number" bind:value={() => getLengthValue(), (v) => setLengthValue(v)} placeholder="Enter exact length">
|
||||
<input class="form-input" type="number" bind:value={() => getLengthValue(), (v) => setLengthValue(v)} placeholder="Enter exact length" aria-label={`${label} length`}>
|
||||
|
||||
{:else if restrictionType === 'Length Range'}
|
||||
<div class="length-range-controls">
|
||||
<div class="length-group">
|
||||
<label>Min Length</label>
|
||||
<input class="form-input" type="number" bind:value={() => getLengthRangeValues().min, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(v, range.max); }} placeholder="0">
|
||||
<label for={`${baseId}-length-min`}>Min Length</label>
|
||||
<input class="form-input" id={`${baseId}-length-min`} type="number" bind:value={() => getLengthRangeValues().min, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(v, range.max); }} placeholder="0">
|
||||
</div>
|
||||
<div class="length-group">
|
||||
<label>Max Length</label>
|
||||
<input class="form-input" type="number" bind:value={() => getLengthRangeValues().max, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(range.min, v); }} placeholder="0">
|
||||
<label for={`${baseId}-length-max`}>Max Length</label>
|
||||
<input class="form-input" id={`${baseId}-length-max`} type="number" bind:value={() => getLengthRangeValues().max, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(range.min, v); }} placeholder="0">
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -656,4 +732,4 @@
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,44 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import type { DocumentState, IdsCardinality, IdsDocument, Specification } from "$src/types/ids";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let activeSpecification = $derived(
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null
|
||||
);
|
||||
|
||||
const getProp = (prop) => {
|
||||
return activeSpecification?.[prop] ?? "";
|
||||
const getProp = (prop: string) => {
|
||||
const value = (activeSpecification as Record<string, unknown>)?.[prop];
|
||||
return typeof value === "string" ? value : "";
|
||||
};
|
||||
|
||||
const setProp = (prop, value) => {
|
||||
activeSpecification[prop] = value;
|
||||
const setProp = (prop: string, value: string) => {
|
||||
if (!activeSpecification) return;
|
||||
(activeSpecification as Record<string, unknown>)[prop] = value;
|
||||
};
|
||||
|
||||
const addIfcVersion = (e, version) => {
|
||||
const addIfcVersion = (e: Event, version: string) => {
|
||||
if (activeSpecification) {
|
||||
if (!("@ifcVersion" in activeSpecification)) activeSpecification["@ifcVersion"] = [];
|
||||
if (e.target.checked) {
|
||||
if (!activeSpecification["@ifcVersion"].includes(version)) {
|
||||
activeSpecification["@ifcVersion"] = [...activeSpecification["@ifcVersion"], version];
|
||||
const target = e.target as HTMLInputElement | null;
|
||||
if (target?.checked) {
|
||||
if (!activeSpecification["@ifcVersion"]?.includes(version)) {
|
||||
activeSpecification["@ifcVersion"] = [...(activeSpecification["@ifcVersion"] ?? []), version];
|
||||
}
|
||||
} else {
|
||||
} else if (activeSpecification["@ifcVersion"]) {
|
||||
activeSpecification["@ifcVersion"] = activeSpecification["@ifcVersion"].filter(v => v !== version);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setUsage = (usage) => {
|
||||
const setUsage = (usage: IdsCardinality) => {
|
||||
if (!activeSpecification) return;
|
||||
if (!activeSpecification.applicability) activeSpecification.applicability = {};
|
||||
|
||||
@@ -60,14 +71,19 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spec-cardinality">Usage</label>
|
||||
<select class="form-input" id="spec-cardinality" value={IDS.getSpecUsage(activeSpecification)} onchange={(e) => setUsage(e.target.value)}>
|
||||
<select
|
||||
class="form-input"
|
||||
id="spec-cardinality"
|
||||
value={IDS.getSpecUsage(activeSpecification)}
|
||||
onchange={(e) => setUsage((e.target as HTMLSelectElement).value as IdsCardinality)}
|
||||
>
|
||||
<option value="required">Required</option>
|
||||
<option value="optional">Optional</option>
|
||||
<option value="prohibited">Prohibited</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label>IFC Version</label>
|
||||
<span class="form-label">IFC Version</span>
|
||||
<div class="radio-group">
|
||||
<label class="radio-label">
|
||||
<input type="checkbox" value="IFC2X3" checked={activeSpecification?.["@ifcVersion"]?.includes('IFC2X3')} onchange={(e) => addIfcVersion(e, 'IFC2X3')}>
|
||||
@@ -92,4 +108,4 @@
|
||||
<textarea class="form-input" id="spec-instructions" bind:value={() => getProp("@instructions"), (v) => setProp("@instructions", v)} placeholder="Enter instructions" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import AppHeader from "$src/components/AppHeader.svelte";
|
||||
import AppRibbon from "$src/components/AppRibbon.svelte";
|
||||
import AppToolbar from "$src/components/AppToolbar.svelte";
|
||||
@@ -11,14 +11,21 @@
|
||||
import IdsViewer from "./IdsViewer.svelte";
|
||||
import SplashScreen from "$src/components/SplashScreen.svelte";
|
||||
import { Toaster } from "$lib/components/ui/sonner";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
||||
import {onMount} from "svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
import type { DocumentState, IdsDocument, Specification } from "$src/types/ids";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let activeSpecification = $derived(
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null
|
||||
);
|
||||
|
||||
async function addNewSpecification() {
|
||||
if (!IDS.Module.activeDocument) return;
|
||||
@@ -28,11 +35,11 @@
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'info' });
|
||||
}
|
||||
|
||||
async function importSpecification(sourceDocId, specIndex) {
|
||||
async function importSpecification(sourceDocId: string, specIndex: number) {
|
||||
if (!IDS.Module.activeDocument || !IDS.Module.documents[sourceDocId]) return;
|
||||
|
||||
try {
|
||||
const sourceDoc = IDS.Module.documents[sourceDocId];
|
||||
const sourceDoc = IDS.Module.documents[sourceDocId] as IdsDocument;
|
||||
const sourceSpec = sourceDoc.specifications?.specification?.[specIndex];
|
||||
|
||||
if (!sourceSpec) {
|
||||
@@ -41,13 +48,13 @@
|
||||
}
|
||||
|
||||
// Create a deep copy of the specification
|
||||
const specCopy = JSON.parse(JSON.stringify(sourceSpec));
|
||||
const specCopy = JSON.parse(JSON.stringify(sourceSpec)) as Specification;
|
||||
|
||||
// Add the copied specification to the current document
|
||||
IDS.Module.documents[IDS.Module.activeDocument].specifications.specification.push(specCopy);
|
||||
|
||||
// Switch to the newly created specification and info tab
|
||||
const currentDoc = IDS.Module.documents[IDS.Module.activeDocument];
|
||||
const currentDoc = IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument;
|
||||
const newSpecIndex = (currentDoc.specifications?.specification?.length || 1) - 1;
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, {
|
||||
activeSpecification: newSpecIndex,
|
||||
@@ -57,17 +64,18 @@
|
||||
success(`Specification "${sourceSpec['@name'] || 'Unnamed'}" imported successfully`);
|
||||
} catch (err) {
|
||||
console.error('Error importing specification:', err);
|
||||
error(`Failed to import specification: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to import specification: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function selectSpecification(index) {
|
||||
function selectSpecification(index: number) {
|
||||
if (IDS.Module.activeDocument) {
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: index });
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSpecification(specIndex) {
|
||||
async function deleteSpecification(specIndex: number) {
|
||||
if (!IDS.Module.activeDocument) return;
|
||||
await IDS.deleteSpecification(IDS.Module.activeDocument, specIndex);
|
||||
}
|
||||
@@ -83,9 +91,24 @@
|
||||
success('IDS document exported successfully');
|
||||
} catch (err) {
|
||||
console.error('Error exporting IDS:', err);
|
||||
error('Error exporting IDS: ' + err.message);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error('Error exporting IDS: ' + message);
|
||||
}
|
||||
}
|
||||
|
||||
const updateActiveDocumentState = (updates: Partial<DocumentState>) => {
|
||||
if (IDS.Module.activeDocument) {
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, updates);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActivation = (event: KeyboardEvent, action: () => void) => {
|
||||
if (event.currentTarget !== event.target) return;
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
action();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if IDS.Module.status != "ready"}
|
||||
@@ -162,16 +185,30 @@
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
<div class="specifications-list scrollbar">
|
||||
<div class="spec-item" class:active={documentState?.activeSpecification === null} onclick={() => { if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); }}>
|
||||
<div
|
||||
class="spec-item"
|
||||
class:active={documentState?.activeSpecification === null}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => { if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); }}
|
||||
onkeydown={(event) => handleActivation(event, () => { if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); })}
|
||||
>
|
||||
<span class="spec-icon">ℹ️</span>
|
||||
<span class="spec-name">IDS Information</span>
|
||||
</div>
|
||||
{#if activeDocument?.specifications?.specification}
|
||||
{#each activeDocument.specifications.specification as spec, index}
|
||||
<div class="spec-item" class:active={documentState?.activeSpecification === index} onclick={() => selectSpecification(index)}>
|
||||
<div
|
||||
class="spec-item"
|
||||
class:active={documentState?.activeSpecification === index}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => selectSpecification(index)}
|
||||
onkeydown={(event) => handleActivation(event, () => selectSpecification(index))}
|
||||
>
|
||||
<span class="spec-icon">📄</span>
|
||||
<span class="spec-name">{spec["@name"] || "Specification " + (index + 1)}</span>
|
||||
<button class="btn-delete" onclick={() => deleteSpecification(index)}>
|
||||
<button class="btn-delete" onclick={() => deleteSpecification(index)} aria-label="Delete specification">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 6L6 18M6 6l12 12"></path>
|
||||
</svg>
|
||||
@@ -192,10 +229,10 @@
|
||||
{:else}
|
||||
<!-- Editor/Viewer Toggle -->
|
||||
<div class="view-mode-toggle">
|
||||
<button class="toggle-btn" class:active={documentState?.viewMode === 'editor'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { viewMode: 'editor', auditReport: null })}>
|
||||
<button class="toggle-btn" class:active={documentState?.viewMode === 'editor'} onclick={() => updateActiveDocumentState({ viewMode: 'editor', auditReport: null })}>
|
||||
Editor
|
||||
</button>
|
||||
<button class="toggle-btn" class:active={documentState?.viewMode === 'viewer'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { viewMode: 'viewer', auditReport: null })}>
|
||||
<button class="toggle-btn" class:active={documentState?.viewMode === 'viewer'} onclick={() => updateActiveDocumentState({ viewMode: 'viewer', auditReport: null })}>
|
||||
Viewer
|
||||
</button>
|
||||
</div>
|
||||
@@ -209,18 +246,18 @@
|
||||
<div class="spec-header">
|
||||
<h2>{activeSpecification ? activeSpecification["@name"] || "Specification" : "Specification"}</h2>
|
||||
<div class="spec-tabs">
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'info'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'info' })}>Info</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'applicability'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'applicability' })}>Applicability</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'requirements'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'requirements' })}>Requirements</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'info'} onclick={() => updateActiveDocumentState({ activeTab: 'info' })}>Info</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'applicability'} onclick={() => updateActiveDocumentState({ activeTab: 'applicability' })}>Applicability</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'requirements'} onclick={() => updateActiveDocumentState({ activeTab: 'requirements' })}>Requirements</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if documentState?.activeTab === 'info'}
|
||||
<SpecificationEditor />
|
||||
{:else if documentState?.activeTab === 'applicability'}
|
||||
<ApplicabilityPanel activeTab />
|
||||
<ApplicabilityPanel />
|
||||
{:else if documentState?.activeTab === 'requirements'}
|
||||
<RequirementsPanel activeTab />
|
||||
<RequirementsPanel />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
export type IdsCardinality = "required" | "optional" | "prohibited";
|
||||
|
||||
export type RestrictionValue = {
|
||||
"@value": string;
|
||||
};
|
||||
|
||||
export type Restriction = {
|
||||
"@base"?: string;
|
||||
enumeration?: RestrictionValue[];
|
||||
pattern?: RestrictionValue[];
|
||||
length?: RestrictionValue[];
|
||||
minLength?: RestrictionValue[];
|
||||
maxLength?: RestrictionValue[];
|
||||
minInclusive?: RestrictionValue[];
|
||||
maxInclusive?: RestrictionValue[];
|
||||
minExclusive?: RestrictionValue[];
|
||||
maxExclusive?: RestrictionValue[];
|
||||
};
|
||||
|
||||
export type SimpleValue = {
|
||||
simpleValue: string;
|
||||
};
|
||||
|
||||
export type FacetValue = {
|
||||
simpleValue?: string;
|
||||
restriction?: Restriction;
|
||||
};
|
||||
|
||||
export type Facet = Record<string, FacetValue | string | number | boolean | null | undefined>;
|
||||
|
||||
export type FacetClause = Record<string, Facet[] | number | "unbounded" | undefined>;
|
||||
|
||||
export type Specification = {
|
||||
"@name"?: string;
|
||||
"@identifier"?: string;
|
||||
"@description"?: string;
|
||||
"@instructions"?: string;
|
||||
"@ifcVersion"?: string[];
|
||||
applicability?: FacetClause;
|
||||
requirements?: FacetClause;
|
||||
};
|
||||
|
||||
export type IdsInfo = {
|
||||
title?: string;
|
||||
copyright?: string;
|
||||
version?: string;
|
||||
description?: string;
|
||||
author?: string;
|
||||
date?: string;
|
||||
purpose?: string;
|
||||
milestone?: string;
|
||||
};
|
||||
|
||||
export type IdsDocument = {
|
||||
"@xmlns"?: string;
|
||||
"@xmlns:xs"?: string;
|
||||
"@xmlns:xsi"?: string;
|
||||
"@xsi:schemaLocation"?: string;
|
||||
info: IdsInfo;
|
||||
specifications: {
|
||||
specification: Specification[];
|
||||
};
|
||||
};
|
||||
|
||||
export type DocumentState = {
|
||||
activeTab: "info" | "applicability" | "requirements";
|
||||
viewMode: "editor" | "viewer";
|
||||
activeSpecification: number | null;
|
||||
auditReport?: string | null;
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
export type ResultsPercent = number | "N/A";
|
||||
|
||||
export type AuditReportEntity = {
|
||||
reason?: string;
|
||||
element?: unknown;
|
||||
element_type?: unknown;
|
||||
class?: string;
|
||||
predefined_type?: string;
|
||||
name?: string | null;
|
||||
description?: string | null;
|
||||
id?: number;
|
||||
global_id?: string | null;
|
||||
tag?: string | null;
|
||||
type_name?: string;
|
||||
type_tag?: string | null;
|
||||
type_global_id?: string | null;
|
||||
extra_of_type?: number;
|
||||
};
|
||||
|
||||
export type AuditRequirement = {
|
||||
facet_type: string;
|
||||
metadata: Record<string, unknown>;
|
||||
label: string;
|
||||
value: string;
|
||||
description: string;
|
||||
status: boolean;
|
||||
passed_entities: AuditReportEntity[];
|
||||
failed_entities: AuditReportEntity[];
|
||||
total_applicable: number;
|
||||
total_pass: number;
|
||||
total_fail: number;
|
||||
percent_pass: ResultsPercent;
|
||||
instructions?: string;
|
||||
total_failed_entities?: number;
|
||||
total_omitted_failures?: number;
|
||||
has_omitted_failures?: boolean;
|
||||
total_passed_entities?: number;
|
||||
total_omitted_passes?: number;
|
||||
has_omitted_passes?: boolean;
|
||||
};
|
||||
|
||||
export type AuditSpecification = {
|
||||
name: string;
|
||||
description: string;
|
||||
instructions: string;
|
||||
status: boolean;
|
||||
is_ifc_version: boolean;
|
||||
total_applicable: number;
|
||||
total_applicable_pass: number;
|
||||
total_applicable_fail: number;
|
||||
percent_applicable_pass: ResultsPercent;
|
||||
total_checks: number;
|
||||
total_checks_pass: number;
|
||||
total_checks_fail: number;
|
||||
percent_checks_pass: ResultsPercent;
|
||||
cardinality: string;
|
||||
applicability: string[];
|
||||
requirements: AuditRequirement[];
|
||||
total_requirements?: number;
|
||||
total_requirements_pass?: number;
|
||||
};
|
||||
|
||||
export type AuditReportData = {
|
||||
title: string;
|
||||
date: string;
|
||||
filepath: string | null;
|
||||
filename: string | null;
|
||||
hide_skipped: boolean;
|
||||
specifications: AuditSpecification[];
|
||||
status: boolean;
|
||||
total_specifications: number;
|
||||
total_specifications_pass: number;
|
||||
total_specifications_fail: number;
|
||||
percent_specifications_pass: ResultsPercent;
|
||||
total_requirements: number;
|
||||
total_requirements_pass: number;
|
||||
total_requirements_fail: number;
|
||||
percent_requirements_pass: ResultsPercent;
|
||||
total_checks: number;
|
||||
total_checks_pass: number;
|
||||
total_checks_fail: number;
|
||||
percent_checks_pass: ResultsPercent;
|
||||
};
|
||||
|
||||
export type AuditReport = {
|
||||
id: string;
|
||||
modelId: string;
|
||||
modelName: string;
|
||||
document: string;
|
||||
date: string;
|
||||
data: AuditReportData;
|
||||
htmlReport?: string | null;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
export type WorkerMessageType =
|
||||
| "init"
|
||||
| "api_call"
|
||||
| "ready"
|
||||
| "api_response"
|
||||
| "error"
|
||||
| "disposed";
|
||||
|
||||
export type WorkerRequest = {
|
||||
type: WorkerMessageType;
|
||||
payload?: Record<string, unknown>;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type WorkerResponse = {
|
||||
type: WorkerMessageType;
|
||||
payload?: Record<string, unknown>;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type ApiCallPayload = {
|
||||
method: string;
|
||||
args?: unknown[];
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"],
|
||||
"$src": ["./src"],
|
||||
"$src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.svelte"]
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import path from "path";
|
||||
import path from "node:path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), svelte()],
|
||||
@@ -11,4 +11,4 @@ export default defineConfig({
|
||||
$src: path.resolve("./src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user