mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 14:33:28 +00:00
Merge pull request #7808 from theseyan/ifctester-improvements-rebased
IfcTester webapp improvements
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
/src/ifcmax/out/
|
/src/ifcmax/out/
|
||||||
/src/ifcwrap/out/
|
/src/ifcwrap/out/
|
||||||
/src/qtviewer/out/
|
/src/qtviewer/out/
|
||||||
|
/src/ifctester/webapp/public/pyodide/
|
||||||
|
|
||||||
/win/BuildDepsCache*.txt
|
/win/BuildDepsCache*.txt
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ dist:
|
|||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
cp -r $(PACKAGE_NAME) build/
|
cp -r $(PACKAGE_NAME) build/
|
||||||
cp pyproject.toml build/
|
cp pyproject.toml build/
|
||||||
|
if [ -f README.md ]; then cp README.md build/; fi
|
||||||
ifeq ($(IS_STABLE), TRUE)
|
ifeq ($(IS_STABLE), TRUE)
|
||||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
|
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
|
||||||
ifdef IS_MODULE
|
ifdef IS_MODULE
|
||||||
|
|||||||
+63
-10
@@ -25,9 +25,19 @@ WEBAPP_BUILD_DIR := $(WEBAPP_DIR)/dist
|
|||||||
PYODIDE_DIR := $(WEBAPP_DIR)/public/pyodide
|
PYODIDE_DIR := $(WEBAPP_DIR)/public/pyodide
|
||||||
PYODIDE_VERSION := 0.28.0
|
PYODIDE_VERSION := 0.28.0
|
||||||
PYODIDE_URL := https://github.com/pyodide/pyodide/releases/download/$(PYODIDE_VERSION)/pyodide-$(PYODIDE_VERSION).tar.bz2
|
PYODIDE_URL := https://github.com/pyodide/pyodide/releases/download/$(PYODIDE_VERSION)/pyodide-$(PYODIDE_VERSION).tar.bz2
|
||||||
|
WORKER_BIN_DIR := $(WEBAPP_DIR)/public/worker/bin
|
||||||
|
IFCOPENSHELL_WASM_WHEEL := ifcopenshell-0.8.5+a51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl
|
||||||
|
IFCOPENSHELL_WASM_WHEEL_URL := https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-0.8.5%2Ba51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl
|
||||||
|
IFCOPENSHELL_WASM_WHEEL_PATH := $(WORKER_BIN_DIR)/$(IFCOPENSHELL_WASM_WHEEL)
|
||||||
|
WEBAPP_GENERATED_DIR := $(WEBAPP_DIR)/public/worker/generated
|
||||||
|
WEBAPP_IFCTESTER_MANIFEST := $(WEBAPP_GENERATED_DIR)/ifctester.json
|
||||||
|
WEBAPP_IFCTESTER_BUILD_DIR := build-webapp-wheel
|
||||||
|
PACKAGE_WEBAPP_DIR := $(PACKAGE_NAME)/webapp
|
||||||
|
PACKAGE_WEBAPP_WWW_DIR := $(PACKAGE_WEBAPP_DIR)/www
|
||||||
|
|
||||||
.PHONY: webapp-dev
|
.PHONY: webapp-dev
|
||||||
webapp-dev:
|
webapp-dev: pyodide-download ifcopenshell-wasm-download webapp-stage-ifctester-wheel
|
||||||
|
cd $(WEBAPP_DIR) && npm install
|
||||||
cd $(WEBAPP_DIR) && npm run dev
|
cd $(WEBAPP_DIR) && npm run dev
|
||||||
|
|
||||||
.PHONY: pyodide-download
|
.PHONY: pyodide-download
|
||||||
@@ -62,8 +72,43 @@ pyodide-download:
|
|||||||
echo "Pyodide $(PYODIDE_VERSION) prepared in $(PYODIDE_DIR)"; \
|
echo "Pyodide $(PYODIDE_VERSION) prepared in $(PYODIDE_DIR)"; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
.PHONY: ifcopenshell-wasm-download
|
||||||
|
ifcopenshell-wasm-download:
|
||||||
|
@if [ -f "$(IFCOPENSHELL_WASM_WHEEL_PATH)" ]; then \
|
||||||
|
echo "IfcOpenShell wasm wheel already exists at $(IFCOPENSHELL_WASM_WHEEL_PATH), skipping download"; \
|
||||||
|
else \
|
||||||
|
echo "Downloading IfcOpenShell wasm wheel..."; \
|
||||||
|
mkdir -p $(WORKER_BIN_DIR); \
|
||||||
|
rm -f $(WORKER_BIN_DIR)/ifcopenshell-*.whl; \
|
||||||
|
curl -fL -o "$(IFCOPENSHELL_WASM_WHEEL_PATH)" "$(IFCOPENSHELL_WASM_WHEEL_URL)"; \
|
||||||
|
echo "IfcOpenShell wasm wheel prepared at $(IFCOPENSHELL_WASM_WHEEL_PATH)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: webapp-stage-ifctester-wheel
|
||||||
|
webapp-stage-ifctester-wheel:
|
||||||
|
rm -rf $(WEBAPP_GENERATED_DIR)
|
||||||
|
rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)
|
||||||
|
mkdir -p $(WEBAPP_IFCTESTER_BUILD_DIR)
|
||||||
|
cp -r $(PACKAGE_NAME) $(WEBAPP_IFCTESTER_BUILD_DIR)/
|
||||||
|
rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/webapp
|
||||||
|
cp pyproject.toml $(WEBAPP_IFCTESTER_BUILD_DIR)/
|
||||||
|
cp README.md $(WEBAPP_IFCTESTER_BUILD_DIR)/
|
||||||
|
ifeq ($(IS_STABLE), TRUE)
|
||||||
|
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/pyproject.toml
|
||||||
|
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/__init__.py
|
||||||
|
else
|
||||||
|
$(SED) 's/version = "0.0.0"/version = "$(VERSION)a$(VERSION_DATE)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/pyproject.toml
|
||||||
|
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/__init__.py
|
||||||
|
endif
|
||||||
|
cd $(WEBAPP_IFCTESTER_BUILD_DIR) && $(PYTHON) -m venv env --system-site-packages && . env/$(VENV_ACTIVATE) && python -m pip install build && python -m build --wheel --no-isolation
|
||||||
|
mkdir -p $(WEBAPP_GENERATED_DIR)
|
||||||
|
wheel=$$(basename $(WEBAPP_IFCTESTER_BUILD_DIR)/dist/$(PACKAGE_NAME)-*.whl); \
|
||||||
|
cp "$(WEBAPP_IFCTESTER_BUILD_DIR)/dist/$$wheel" "$(WEBAPP_GENERATED_DIR)/$$wheel"; \
|
||||||
|
printf '{\n "wheel_url": "/worker/generated/%s"\n}\n' "$$wheel" > "$(WEBAPP_IFCTESTER_MANIFEST)"
|
||||||
|
rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)
|
||||||
|
|
||||||
.PHONY: webapp-build
|
.PHONY: webapp-build
|
||||||
webapp-build: pyodide-download
|
webapp-build: pyodide-download ifcopenshell-wasm-download webapp-stage-ifctester-wheel
|
||||||
cd $(WEBAPP_DIR) && npm install
|
cd $(WEBAPP_DIR) && npm install
|
||||||
cd $(WEBAPP_DIR) && npm run build
|
cd $(WEBAPP_DIR) && npm run build
|
||||||
|
|
||||||
@@ -76,22 +121,30 @@ clean:
|
|||||||
rm -rf $(WEBAPP_BUILD_DIR)
|
rm -rf $(WEBAPP_BUILD_DIR)
|
||||||
rm -rf $(WEBAPP_DIR)/node_modules
|
rm -rf $(WEBAPP_DIR)/node_modules
|
||||||
rm -rf $(PYODIDE_DIR)
|
rm -rf $(PYODIDE_DIR)
|
||||||
|
rm -f $(WORKER_BIN_DIR)/ifcopenshell-*.whl
|
||||||
|
rm -rf $(WEBAPP_GENERATED_DIR)
|
||||||
|
rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)
|
||||||
rm -rf $(PACKAGE_NAME)/webapp
|
rm -rf $(PACKAGE_NAME)/webapp
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
|
|
||||||
.PHONY: dist
|
.PHONY: python-dist
|
||||||
dist: webapp-prepare
|
python-dist:
|
||||||
|
rm -rf dist
|
||||||
# For some reason OS is not initalized when we call common.mk dist, which matters on Windows.
|
# For some reason OS is not initalized when we call common.mk dist, which matters on Windows.
|
||||||
# So we pass it explicitly.
|
# So we pass it explicitly.
|
||||||
$(MAKE) -f ../common.mk dist PACKAGE_NAME=$(PACKAGE_NAME) OS=$(OS)
|
$(MAKE) -f ../common.mk dist PACKAGE_NAME=$(PACKAGE_NAME) OS=$(OS) IS_STABLE=$(IS_STABLE)
|
||||||
|
|
||||||
|
.PHONY: dist
|
||||||
|
dist: webapp-prepare
|
||||||
|
$(MAKE) python-dist OS=$(OS) IS_STABLE=$(IS_STABLE)
|
||||||
|
|
||||||
.PHONY: webapp-prepare
|
.PHONY: webapp-prepare
|
||||||
webapp-prepare: webapp-build
|
webapp-prepare: webapp-build
|
||||||
rm -rf $(PACKAGE_NAME)/webapp/www/*
|
rm -rf $(PACKAGE_WEBAPP_WWW_DIR)
|
||||||
mkdir -p $(PACKAGE_NAME)/webapp/www
|
mkdir -p $(PACKAGE_WEBAPP_WWW_DIR)
|
||||||
cp -r $(WEBAPP_BUILD_DIR)/* $(PACKAGE_NAME)/webapp/www/
|
cp -r $(WEBAPP_BUILD_DIR)/* $(PACKAGE_WEBAPP_WWW_DIR)/
|
||||||
cp $(WEBAPP_DIR)/__init__.py $(PACKAGE_NAME)/webapp/__init__.py
|
cp $(WEBAPP_DIR)/__init__.py $(PACKAGE_WEBAPP_DIR)/__init__.py
|
||||||
cp $(WEBAPP_DIR)/serve.py $(PACKAGE_NAME)/webapp/serve.py
|
cp $(WEBAPP_DIR)/serve.py $(PACKAGE_WEBAPP_DIR)/serve.py
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
public/worker/bin/ifcopenshell-*.whl
|
||||||
|
public/worker/generated
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
@@ -24,4 +26,4 @@ dist-ssr
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
.claude
|
.claude
|
||||||
experiment/*
|
experiment/*
|
||||||
|
|||||||
@@ -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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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"
|
"svelte-spa-router": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@internationalized/date": "^3.8.1",
|
"@internationalized/date": "^3.8.1",
|
||||||
"@lucide/svelte": "^0.515.0",
|
"@lucide/svelte": "^0.515.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
@@ -25,11 +26,13 @@
|
|||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"sass-embedded": "^1.89.0",
|
"sass-embedded": "^1.89.0",
|
||||||
"svelte": "^5.53.6",
|
"svelte": "^5.53.6",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
"svelte-sonner": "^1.0.5",
|
"svelte-sonner": "^1.0.5",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.3.0",
|
||||||
"tailwind-variants": "^1.0.0",
|
"tailwind-variants": "^1.0.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"tw-animate-css": "^1.3.2",
|
"tw-animate-css": "^1.3.2",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
"vite": "^6.4.1"
|
"vite": "^6.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -47,6 +50,170 @@
|
|||||||
"node": ">=6.0.0"
|
"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": {
|
"node_modules/@bufbuild/protobuf": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.1.tgz",
|
||||||
@@ -1468,6 +1635,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT/X11"
|
"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": {
|
"node_modules/chownr": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||||
@@ -2126,6 +2309,16 @@
|
|||||||
"svelte": "^5.7.0"
|
"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": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -2200,6 +2393,20 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"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": {
|
"node_modules/regexparam": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz",
|
||||||
@@ -2281,6 +2488,19 @@
|
|||||||
"tslib": "^2.1.0"
|
"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": {
|
"node_modules/sass-embedded": {
|
||||||
"version": "1.89.0",
|
"version": "1.89.0",
|
||||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.0.tgz",
|
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.0.tgz",
|
||||||
@@ -2783,6 +3003,30 @@
|
|||||||
"node": ">=18"
|
"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": {
|
"node_modules/svelte-sonner": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz",
|
||||||
@@ -2983,6 +3227,20 @@
|
|||||||
"url": "https://github.com/sponsors/Wombosvideo"
|
"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": {
|
"node_modules/uuid": {
|
||||||
"version": "8.3.2",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
|||||||
@@ -6,11 +6,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"check": "tsc -p tsconfig.json --noEmit && svelte-check && biome lint .",
|
||||||
|
"lint": "biome lint .",
|
||||||
|
"lint:fix": "biome lint --write .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"deploy": "npm run build && npx wrangler pages deploy dist"
|
"deploy": "npm run build && npx wrangler pages deploy dist"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@internationalized/date": "^3.8.1",
|
"@internationalized/date": "^3.8.1",
|
||||||
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@lucide/svelte": "^0.515.0",
|
"@lucide/svelte": "^0.515.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
@@ -19,10 +23,12 @@
|
|||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"sass-embedded": "^1.89.0",
|
"sass-embedded": "^1.89.0",
|
||||||
"svelte": "^5.53.6",
|
"svelte": "^5.53.6",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
"svelte-sonner": "^1.0.5",
|
"svelte-sonner": "^1.0.5",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.3.0",
|
||||||
"tailwind-variants": "^1.0.0",
|
"tailwind-variants": "^1.0.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
"tw-animate-css": "^1.3.2",
|
"tw-animate-css": "^1.3.2",
|
||||||
"vite": "^6.4.1"
|
"vite": "^6.4.1"
|
||||||
},
|
},
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -22,9 +22,6 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
bonsai_lib_path = os.environ.get("BONSAI_LIB_PATH")
|
bonsai_lib_path = os.environ.get("BONSAI_LIB_PATH")
|
||||||
print(os.environ)
|
|
||||||
print(bonsai_lib_path)
|
|
||||||
bonsai_version = os.environ.get("BONSAI_VERSION")
|
|
||||||
|
|
||||||
if bonsai_lib_path:
|
if bonsai_lib_path:
|
||||||
sys.path.insert(0, bonsai_lib_path)
|
sys.path.insert(0, bonsai_lib_path)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import Router from 'svelte-spa-router';
|
import Router from 'svelte-spa-router';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
</script>
|
</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 Menubar from "$lib/components/ui/menubar";
|
||||||
import * as Dialog from "$lib/components/ui/dialog";
|
import * as Dialog from "$lib/components/ui/dialog";
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
import * as IDS from "$src/modules/api/ids.svelte";
|
||||||
import * as API from "$src/modules/api/api.svelte.js";
|
import * as API from "$src/modules/api/api.svelte";
|
||||||
import { error, success, info } from "$src/modules/utils/toast.svelte.js";
|
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||||
|
|
||||||
let { isOpen = false } = $props();
|
let { isOpen = false } : { isOpen?: boolean } = $props();
|
||||||
|
|
||||||
function openForum() {
|
function openForum() {
|
||||||
window.open('https://community.osarch.org', '_blank');
|
window.open('https://community.osarch.org', '_blank');
|
||||||
@@ -23,8 +23,9 @@
|
|||||||
try {
|
try {
|
||||||
await IDS.openDocument();
|
await IDS.openDocument();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message !== 'File selection cancelled') {
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
error('Error opening file: ' + err.message);
|
if (message !== 'File selection cancelled') {
|
||||||
|
error(`Error opening file: ${message}`);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
success('Audit completed successfully');
|
success('Audit completed successfully');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Audit failed: ", err);
|
console.error("Audit failed: ", err);
|
||||||
error(`Audit failed: check console for details`);
|
error("Audit failed: check console for details");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -142,4 +143,4 @@
|
|||||||
</Dialog.Close>
|
</Dialog.Close>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { Module } from "$src/modules/api/ids.svelte";
|
||||||
import { Module } from "$src/modules/api/ids.svelte.js";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="app-ribbon">
|
<div class="app-ribbon">
|
||||||
@@ -20,4 +19,4 @@
|
|||||||
<span>Error</span>
|
<span>Error</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import * as Tooltip from "$lib/components/ui/tooltip";
|
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 { IFCModels, openIfc, unloadIfc, runAudit } from "$src/modules/api/api.svelte";
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
import * as IDS from "$src/modules/api/ids.svelte";
|
||||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||||
import { ChevronRightIcon, LinkIcon, XIcon } from "@lucide/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 { onMount } from 'svelte';
|
||||||
|
import type { AuditReport } from "$src/types/report";
|
||||||
|
|
||||||
let isAuditing = $state(false);
|
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);
|
let isMinimized = $state(false);
|
||||||
|
|
||||||
const handleLoadModel = async () => {
|
const handleLoadModel = async () => {
|
||||||
@@ -16,15 +18,17 @@
|
|||||||
await openIfc();
|
await openIfc();
|
||||||
success('IFC model loaded successfully');
|
success('IFC model loaded successfully');
|
||||||
} catch (err) {
|
} 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 {
|
try {
|
||||||
await unloadIfc(modelId);
|
await unloadIfc(modelId);
|
||||||
} catch (err) {
|
} 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');
|
success('Audit completed successfully');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Audit failed: ", err);
|
console.error("Audit failed: ", err);
|
||||||
error(`Audit failed: check console for details`);
|
error("Audit failed: check console for details");
|
||||||
} finally {
|
} finally {
|
||||||
isAuditing = false;
|
isAuditing = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewAuditReport = (auditId) => {
|
const handleViewAuditReport = (auditId: string) => {
|
||||||
const auditReport = IFCModels.audits.find(audit => audit.id === auditId);
|
const auditReport = IFCModels.audits.find(audit => audit.id === auditId) as AuditReport | undefined;
|
||||||
if (!auditReport) return;
|
if (!auditReport) return;
|
||||||
|
|
||||||
// Switch to the IDS document that was used for this audit
|
// 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'];
|
const units = ['B', 'KB', 'MB', 'GB'];
|
||||||
let size = bytes;
|
let size = bytes;
|
||||||
let unitIndex = 0;
|
let unitIndex = 0;
|
||||||
@@ -91,7 +95,7 @@
|
|||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<Tooltip.Provider>
|
<Tooltip.Provider>
|
||||||
{#if isMinimized}
|
{#if isMinimized}
|
||||||
<Tooltip.Root disableHoverableContent="true">
|
<Tooltip.Root disableHoverableContent>
|
||||||
<Tooltip.Trigger>
|
<Tooltip.Trigger>
|
||||||
<button class="tb-btn expand-btn" onclick={() => isMinimized = false} aria-label="Expand Toolbar">
|
<button class="tb-btn expand-btn" onclick={() => isMinimized = false} aria-label="Expand Toolbar">
|
||||||
<ChevronRightIcon size={24} />
|
<ChevronRightIcon size={24} />
|
||||||
@@ -102,7 +106,7 @@
|
|||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Root>
|
</Tooltip.Root>
|
||||||
{/if}
|
{/if}
|
||||||
<Tooltip.Root disableHoverableContent="true">
|
<Tooltip.Root disableHoverableContent>
|
||||||
<Tooltip.Trigger>
|
<Tooltip.Trigger>
|
||||||
<button class="tb-btn {activeTab === 'home' ? 'active' : ''}" onclick={() => activeTab = 'home'} aria-label="Home">
|
<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">
|
<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>
|
<p>Home</p>
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Root>
|
</Tooltip.Root>
|
||||||
<Tooltip.Root disableHoverableContent="true">
|
<Tooltip.Root disableHoverableContent>
|
||||||
<Tooltip.Trigger>
|
<Tooltip.Trigger>
|
||||||
<button class="tb-btn {activeTab === 'bonsai' ? 'active' : ''}" onclick={() => activeTab = 'bonsai'} aria-label="Bonsai Integration">
|
<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">
|
<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;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
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>
|
</script>
|
||||||
|
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
@@ -63,4 +65,4 @@
|
|||||||
Part Of
|
Part Of
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
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;
|
IDS.Module.activeDocument = docId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDocument(docId) {
|
function closeDocument(docId: string) {
|
||||||
IDS.deleteDocument(docId);
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="ids-tabs">
|
<div class="ids-tabs">
|
||||||
@@ -15,10 +24,13 @@
|
|||||||
<div
|
<div
|
||||||
class="ids-tab"
|
class="ids-tab"
|
||||||
class:active={IDS.Module.activeDocument === docId}
|
class:active={IDS.Module.activeDocument === docId}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
onclick={() => switchDocument(docId)}
|
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
|
<button
|
||||||
class="tab-close"
|
class="tab-close"
|
||||||
onclick={(e) => { e.stopPropagation(); closeDocument(docId); }}
|
onclick={(e) => { e.stopPropagation(); closeDocument(docId); }}
|
||||||
@@ -31,4 +43,4 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="filler-tab"></div>
|
<div class="filler-tab"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"wasm": {
|
"wasm": {
|
||||||
"wheel_url": "/worker/bin/ifcopenshell-0.8.3+bb329af-cp313-cp313-emscripten_4_0_9_wasm32.whl",
|
"wheel_url": "/worker/bin/ifcopenshell-0.8.5+a51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl",
|
||||||
"odfpy_url": "/worker/bin/odfpy-1.4.2-py2.py3-none-any.whl",
|
"odfpy_url": "/worker/bin/odfpy-1.4.2-py2.py3-none-any.whl",
|
||||||
"api_py_url": "/worker/api.py",
|
"api_py_url": "/worker/api.py"
|
||||||
"pyodide_url": "https://cdn.jsdelivr.net/pyodide/v0.28.0/full/pyodide.js"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -576,7 +576,8 @@ html, body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
label {
|
label,
|
||||||
|
.form-label {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
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>
|
</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 { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
import XIcon from "@lucide/svelte/icons/x";
|
import XIcon from "@lucide/svelte/icons/x";
|
||||||
import * as Dialog from "./index.js";
|
import * as Dialog from "./index";
|
||||||
import { cn } from "$lib/utils.js";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -11,7 +20,7 @@
|
|||||||
children,
|
children,
|
||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dialog.Portal {...portalProps}>
|
<Dialog.Portal {...portalProps}>
|
||||||
@@ -35,4 +44,4 @@
|
|||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
{/if}
|
{/if}
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DialogPrimitive.Description
|
<DialogPrimitive.Description
|
||||||
@@ -14,4 +19,4 @@
|
|||||||
data-slot="dialog-description"
|
data-slot="dialog-description"
|
||||||
class={cn("text-muted-foreground text-sm", className)}
|
class={cn("text-muted-foreground text-sm", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
class?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -15,4 +23,4 @@
|
|||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
class?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -16,4 +23,4 @@
|
|||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
@@ -17,4 +22,4 @@
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DialogPrimitive.Title
|
<DialogPrimitive.Title
|
||||||
@@ -14,4 +19,4 @@
|
|||||||
data-slot="dialog-title"
|
data-slot="dialog-title"
|
||||||
class={cn("text-lg leading-none", className)}
|
class={cn("text-lg leading-none", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
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>
|
</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 { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
import CheckIcon from "@lucide/svelte/icons/check";
|
import CheckIcon from "@lucide/svelte/icons/check";
|
||||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
checked = $bindable(false),
|
checked = $bindable(false),
|
||||||
@@ -10,7 +18,7 @@
|
|||||||
class: className,
|
class: className,
|
||||||
children: childrenProp,
|
children: childrenProp,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
@@ -34,4 +42,4 @@
|
|||||||
</span>
|
</span>
|
||||||
{@render childrenProp?.()}
|
{@render childrenProp?.()}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
|||||||
+11
-4
@@ -1,14 +1,21 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils";
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
sideOffset?: number;
|
||||||
|
portalProps?: Record<string, unknown>;
|
||||||
|
class?: string;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
sideOffset = 4,
|
sideOffset = 4,
|
||||||
portalProps,
|
portalProps,
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||||
@@ -22,4 +29,4 @@
|
|||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuPrimitive.Portal>
|
</DropdownMenuPrimitive.Portal>
|
||||||
|
|||||||
+9
-4
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
inset,
|
inset,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.GroupHeading
|
<DropdownMenuPrimitive.GroupHeading
|
||||||
@@ -15,4 +20,4 @@
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
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>
|
</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>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils";
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
class?: string;
|
||||||
|
inset?: boolean;
|
||||||
|
variant?: string;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
inset,
|
inset,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
@@ -21,4 +28,4 @@
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+12
-4
@@ -1,12 +1,20 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
inset,
|
inset,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -17,4 +25,4 @@
|
|||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+8
-3
@@ -1,11 +1,16 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
value?: string;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
value = $bindable(),
|
value = $bindable(),
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.RadioGroup
|
<DropdownMenuPrimitive.RadioGroup
|
||||||
@@ -13,4 +18,4 @@
|
|||||||
bind:value
|
bind:value
|
||||||
data-slot="dropdown-menu-radio-group"
|
data-slot="dropdown-menu-radio-group"
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+12
-4
@@ -1,14 +1,22 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children: childrenProp,
|
children: childrenProp,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
@@ -28,4 +36,4 @@
|
|||||||
</span>
|
</span>
|
||||||
{@render childrenProp?.({ checked })}
|
{@render childrenProp?.({ checked })}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
|
|||||||
+9
-4
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.Separator
|
<DropdownMenuPrimitive.Separator
|
||||||
@@ -14,4 +19,4 @@
|
|||||||
data-slot="dropdown-menu-separator"
|
data-slot="dropdown-menu-separator"
|
||||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+11
-4
@@ -1,12 +1,19 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
class?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@@ -16,4 +23,4 @@
|
|||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
+9
-4
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
@@ -17,4 +22,4 @@
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+12
-4
@@ -1,7 +1,15 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -9,7 +17,7 @@
|
|||||||
inset,
|
inset,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
@@ -24,4 +32,4 @@
|
|||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
<ChevronRightIcon class="ml-auto size-4" />
|
<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";
|
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>
|
</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 { Menubar as MenubarPrimitive } from "bits-ui";
|
||||||
import CheckIcon from "@lucide/svelte/icons/check";
|
import CheckIcon from "@lucide/svelte/icons/check";
|
||||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
@@ -10,7 +18,7 @@
|
|||||||
indeterminate = $bindable(false),
|
indeterminate = $bindable(false),
|
||||||
children: childrenProp,
|
children: childrenProp,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.CheckboxItem
|
<MenubarPrimitive.CheckboxItem
|
||||||
@@ -34,4 +42,4 @@
|
|||||||
</span>
|
</span>
|
||||||
{@render childrenProp?.()}
|
{@render childrenProp?.()}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</MenubarPrimitive.CheckboxItem>
|
</MenubarPrimitive.CheckboxItem>
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -11,7 +24,7 @@
|
|||||||
side = "bottom",
|
side = "bottom",
|
||||||
portalProps,
|
portalProps,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.Portal {...portalProps}>
|
<MenubarPrimitive.Portal {...portalProps}>
|
||||||
@@ -28,4 +41,4 @@
|
|||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</MenubarPrimitive.Portal>
|
</MenubarPrimitive.Portal>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils";
|
||||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
inset?: boolean;
|
||||||
|
class?: string;
|
||||||
|
} & Record<string, unknown>;
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
inset,
|
inset,
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.GroupHeading
|
<MenubarPrimitive.GroupHeading
|
||||||
@@ -15,4 +20,4 @@
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
class={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
|
class={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</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 { 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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -8,7 +15,7 @@
|
|||||||
inset = undefined,
|
inset = undefined,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.Item
|
<MenubarPrimitive.Item
|
||||||
@@ -21,4 +28,4 @@
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
inset,
|
inset,
|
||||||
children,
|
children,
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -17,4 +24,4 @@
|
|||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children: childrenProp,
|
children: childrenProp,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.RadioItem
|
<MenubarPrimitive.RadioItem
|
||||||
@@ -28,4 +36,4 @@
|
|||||||
</span>
|
</span>
|
||||||
{@render childrenProp?.({ checked })}
|
{@render childrenProp?.({ checked })}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</MenubarPrimitive.RadioItem>
|
</MenubarPrimitive.RadioItem>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.Separator
|
<MenubarPrimitive.Separator
|
||||||
@@ -14,4 +19,4 @@
|
|||||||
data-slot="menubar-separator"
|
data-slot="menubar-separator"
|
||||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
class?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@@ -16,4 +23,4 @@
|
|||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.SubContent
|
<MenubarPrimitive.SubContent
|
||||||
@@ -17,4 +22,4 @@
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -9,7 +17,7 @@
|
|||||||
inset = undefined,
|
inset = undefined,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.SubTrigger
|
<MenubarPrimitive.SubTrigger
|
||||||
@@ -24,4 +32,4 @@
|
|||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
<ChevronRightIcon class="ml-auto size-4" />
|
<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 { 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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.Trigger
|
<MenubarPrimitive.Trigger
|
||||||
@@ -17,4 +22,4 @@
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MenubarPrimitive.Root
|
<MenubarPrimitive.Root
|
||||||
@@ -17,4 +22,4 @@
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Toaster as Sonner } from "svelte-sonner";
|
import { Toaster as Sonner } from "svelte-sonner";
|
||||||
import { mode } from "mode-watcher";
|
import { mode } from "mode-watcher";
|
||||||
|
|
||||||
let { ...restProps } = $props();
|
let { ...restProps } : Record<string, unknown> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Sonner
|
<Sonner
|
||||||
@@ -10,4 +10,4 @@
|
|||||||
class="toaster group"
|
class="toaster group"
|
||||||
style="--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border);"
|
style="--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border);"
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
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 {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -10,7 +22,7 @@
|
|||||||
children,
|
children,
|
||||||
arrowClasses,
|
arrowClasses,
|
||||||
...restProps
|
...restProps
|
||||||
} = $props();
|
} : Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TooltipPrimitive.Portal>
|
<TooltipPrimitive.Portal>
|
||||||
@@ -42,4 +54,4 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</TooltipPrimitive.Arrow>
|
</TooltipPrimitive.Arrow>
|
||||||
</TooltipPrimitive.Content>
|
</TooltipPrimitive.Content>
|
||||||
</TooltipPrimitive.Portal>
|
</TooltipPrimitive.Portal>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
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>
|
</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 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 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: [],
|
entityClasses: [],
|
||||||
materialCategories: [],
|
materialCategories: [],
|
||||||
classificationSystems: {},
|
classificationSystems: {},
|
||||||
@@ -10,13 +33,13 @@ export let Autocompletions = $state({
|
|||||||
isLoaded: false
|
isLoaded: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export let IFCModels = $state({
|
export const IFCModels: IfcModelState = $state({
|
||||||
models: [],
|
models: [],
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
audits: []
|
audits: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const id = hyperid();
|
const id: () => string = hyperid();
|
||||||
|
|
||||||
// Preload autocompletions on initialization
|
// Preload autocompletions on initialization
|
||||||
wasm.init().then(async () => {
|
wasm.init().then(async () => {
|
||||||
@@ -30,26 +53,30 @@ export async function preloadAutocompletions() {
|
|||||||
// Entity classes
|
// Entity classes
|
||||||
const entitySets = await Promise.all(
|
const entitySets = await Promise.all(
|
||||||
schemas.map(schema => wasm.getAllEntityClasses(schema))
|
schemas.map(schema => wasm.getAllEntityClasses(schema))
|
||||||
);
|
) as string[][];
|
||||||
const allEntities = new Set();
|
const allEntities = new Set<string>();
|
||||||
entitySets.forEach(entities => {
|
for (const entities of entitySets) {
|
||||||
entities.forEach(entity => allEntities.add(entity.toUpperCase()));
|
for (const entity of entities) {
|
||||||
});
|
allEntities.add(entity.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Data types
|
// Data types
|
||||||
const dataTypeSets = await Promise.all(
|
const dataTypeSets = await Promise.all(
|
||||||
schemas.map(schema => wasm.getAllDataTypes(schema))
|
schemas.map(schema => wasm.getAllDataTypes(schema))
|
||||||
);
|
) as Record<string, string>[];
|
||||||
const allDataTypes = new Set();
|
const allDataTypes = new Set<string>();
|
||||||
dataTypeSets.forEach(dataTypes => {
|
for (const dataTypes of dataTypeSets) {
|
||||||
Object.keys(dataTypes).forEach(dataType => allDataTypes.add(dataType));
|
for (const dataType of Object.keys(dataTypes as Record<string, string>)) {
|
||||||
});
|
allDataTypes.add(dataType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Material categories and Classification systems
|
// Material categories and Classification systems
|
||||||
const [materialCategories, classificationSystems] = await Promise.all([
|
const [materialCategories, classificationSystems] = await Promise.all([
|
||||||
wasm.getMaterialCategories(),
|
wasm.getMaterialCategories(),
|
||||||
wasm.getStandardClassificationSystems()
|
wasm.getStandardClassificationSystems()
|
||||||
]);
|
]) as [string[], AutocompletionState["classificationSystems"]];
|
||||||
|
|
||||||
// Cache autocompletions
|
// Cache autocompletions
|
||||||
Autocompletions.entityClasses = Array.from(allEntities).sort();
|
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);
|
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);
|
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);
|
return await wasm.getApplicablePsets(schema, entity, predefinedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +119,7 @@ export function getDataTypes() {
|
|||||||
return Autocompletions.dataTypes;
|
return Autocompletions.dataTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadIfc(file) {
|
export async function loadIfc(file: File): Promise<IfcModel> {
|
||||||
try {
|
try {
|
||||||
IFCModels.isLoading = true;
|
IFCModels.isLoading = true;
|
||||||
|
|
||||||
@@ -100,10 +127,10 @@ export async function loadIfc(file) {
|
|||||||
const uint8Array = new Uint8Array(arrayBuffer);
|
const uint8Array = new Uint8Array(arrayBuffer);
|
||||||
|
|
||||||
// Load IFC model
|
// 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
|
// Add to models list
|
||||||
const model = {
|
const model: IfcModel = {
|
||||||
id: ifcId,
|
id: ifcId,
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
fileSize: file.size,
|
fileSize: file.size,
|
||||||
@@ -121,7 +148,7 @@ export async function loadIfc(file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unloadIfc(modelId) {
|
export async function unloadIfc(modelId: string) {
|
||||||
try {
|
try {
|
||||||
// Unload model
|
// Unload model
|
||||||
await wasm.unloadIfc(modelId);
|
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 {
|
try {
|
||||||
let idsBytes;
|
let idsBytes: Uint8Array;
|
||||||
if (typeof idsData === 'string') {
|
if (typeof idsData === 'string') {
|
||||||
idsBytes = new TextEncoder().encode(idsData);
|
idsBytes = new TextEncoder().encode(idsData);
|
||||||
} else if (idsData instanceof ArrayBuffer) {
|
} else if (idsData instanceof ArrayBuffer) {
|
||||||
@@ -148,7 +175,7 @@ export async function auditIfc(modelId, idsData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run audit
|
// 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}`);
|
console.log(`Audit completed for model ${modelId}`);
|
||||||
return auditResult;
|
return auditResult;
|
||||||
@@ -163,13 +190,14 @@ export function getLoadedModels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function openIfc() {
|
export async function openIfc() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const fileInput = document.createElement('input');
|
const fileInput = document.createElement('input');
|
||||||
fileInput.type = 'file';
|
fileInput.type = 'file';
|
||||||
fileInput.accept = '.ifc';
|
fileInput.accept = '.ifc';
|
||||||
|
|
||||||
fileInput.onchange = async (event) => {
|
fileInput.onchange = async (event) => {
|
||||||
const file = event.target.files[0];
|
const target = event.target as HTMLInputElement | null;
|
||||||
|
const file = target?.files?.[0];
|
||||||
if (!file) {
|
if (!file) {
|
||||||
reject(new Error('No file selected'));
|
reject(new Error('No file selected'));
|
||||||
return;
|
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);
|
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);
|
const model = getIfcById(modelId);
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
const auditReport = {
|
const auditReport: AuditReport = {
|
||||||
id: id(),
|
id: id(),
|
||||||
modelId: modelId,
|
modelId: modelId,
|
||||||
modelName: model.fileName,
|
modelName: model.fileName,
|
||||||
@@ -216,19 +249,19 @@ export function createAuditReport(modelId, document, auditData, htmlReport = nul
|
|||||||
return auditReport;
|
return auditReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAuditReportsForIfc(modelId) {
|
export function getAuditReportsForIfc(modelId: string) {
|
||||||
return IFCModels.audits.filter(audit => audit.modelId === modelId);
|
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);
|
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);
|
IFCModels.audits = IFCModels.audits.filter(audit => audit.document !== document);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadAuditReport(auditId) {
|
export async function downloadAuditReport(auditId: string) {
|
||||||
const audit = getAuditReportById(auditId);
|
const audit = getAuditReportById(auditId);
|
||||||
if (!audit || !audit.htmlReport) {
|
if (!audit || !audit.htmlReport) {
|
||||||
throw new Error('HTML report not available for this audit');
|
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
|
// Get IDS document title for filename
|
||||||
let filename = 'report.html';
|
let filename = 'report.html';
|
||||||
if (audit.document && IDS.Module.documents[audit.document]) {
|
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';
|
const title = doc.info?.title || 'untitled';
|
||||||
filename = `report_${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.html`;
|
filename = `report_${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.html`;
|
||||||
}
|
}
|
||||||
@@ -270,9 +303,12 @@ export async function runAudit() {
|
|||||||
|
|
||||||
// Get the active IDS document XML
|
// Get the active IDS document XML
|
||||||
const idsXml = await IDS.exportActiveDocument();
|
const idsXml = await IDS.exportActiveDocument();
|
||||||
|
if (!idsXml) {
|
||||||
|
throw new Error('Failed to export IDS document');
|
||||||
|
}
|
||||||
|
|
||||||
// Run audit on all loaded models
|
// Run audit on all loaded models
|
||||||
let firstAuditReport = null;
|
let firstAuditReport: AuditReport | undefined;
|
||||||
for (const model of IFCModels.models) {
|
for (const model of IFCModels.models) {
|
||||||
const result = await auditIfc(model.id, idsXml);
|
const result = await auditIfc(model.id, idsXml);
|
||||||
|
|
||||||
@@ -280,7 +316,10 @@ export async function runAudit() {
|
|||||||
const jsonData = result.json || null;
|
const jsonData = result.json || null;
|
||||||
const htmlReport = result.html || 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
|
// Store the first audit report to open in viewer
|
||||||
if (!firstAuditReport) {
|
if (!firstAuditReport) {
|
||||||
+60
-22
@@ -1,12 +1,37 @@
|
|||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
import { IFCModels } from './api.svelte.js';
|
import type { Socket } from 'socket.io-client';
|
||||||
import * as IDS from './ids.svelte.js';
|
import { IFCModels } from './api.svelte';
|
||||||
import { error, success } from '../utils/toast.svelte.js';
|
import * as IDS from './ids.svelte';
|
||||||
|
import { error, success } from '../utils/toast.svelte';
|
||||||
import hyperid from 'hyperid';
|
import hyperid from 'hyperid';
|
||||||
import { onMount } from 'svelte';
|
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||||
|
|
||||||
// Bonsai connection state
|
// 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,
|
enabled: false,
|
||||||
port: null,
|
port: null,
|
||||||
socket: null,
|
socket: null,
|
||||||
@@ -14,8 +39,8 @@ export let Bonsai = $state({
|
|||||||
auditing: false
|
auditing: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const id = hyperid();
|
const id: () => string = hyperid();
|
||||||
const pendingAudits = new Map();
|
const pendingAudits = new Map<string, PendingAudit>();
|
||||||
|
|
||||||
// Check for Bonsai server port in URL parameters
|
// Check for Bonsai server port in URL parameters
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
@@ -29,8 +54,11 @@ if (serverPort) {
|
|||||||
/**
|
/**
|
||||||
* Connect to Bonsai server
|
* Connect to Bonsai server
|
||||||
*/
|
*/
|
||||||
export const connect = () => new Promise((resolve, reject) => {
|
export const connect = () => new Promise<void>((resolve, reject) => {
|
||||||
if (!Bonsai.port) return;
|
if (!Bonsai.port) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Bonsai.socket = io(`ws://127.0.0.1:${Bonsai.port}/ifctester`, {
|
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.connected = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
Bonsai.socket.on('connect_error', (err) => {
|
Bonsai.socket.on('connect_error', (err: Error) => {
|
||||||
Bonsai.connected = false;
|
Bonsai.connected = false;
|
||||||
error(`Failed to connect to Bonsai: ${err.message}`);
|
error(`Failed to connect to Bonsai: ${err.message}`);
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -59,7 +87,8 @@ export const connect = () => new Promise((resolve, reject) => {
|
|||||||
Bonsai.socket.on('error', handleAuditError);
|
Bonsai.socket.on('error', handleAuditError);
|
||||||
|
|
||||||
} catch (err) {
|
} 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);
|
reject(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -93,14 +122,21 @@ export const runAudit = async () => {
|
|||||||
|
|
||||||
// Convert IDS document to XML string
|
// Convert IDS document to XML string
|
||||||
const idsXml = await IDS.exportActiveDocument();
|
const idsXml = await IDS.exportActiveDocument();
|
||||||
|
if (!idsXml) {
|
||||||
|
throw new Error('Failed to export IDS document');
|
||||||
|
}
|
||||||
|
|
||||||
const requestId = id();
|
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
|
// Store request with resolve/reject functions
|
||||||
pendingAudits.set(requestId, { resolve, reject });
|
pendingAudits.set(requestId, { resolve, reject });
|
||||||
|
|
||||||
Bonsai.socket.emit('audit_ids', {
|
socket.emit('audit_ids', {
|
||||||
id: requestId,
|
id: requestId,
|
||||||
ids: idsXml
|
ids: idsXml
|
||||||
});
|
});
|
||||||
@@ -108,7 +144,8 @@ export const runAudit = async () => {
|
|||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Bonsai.auditing = false;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -117,7 +154,7 @@ export const runAudit = async () => {
|
|||||||
* Handles audit results from Bonsai server
|
* Handles audit results from Bonsai server
|
||||||
* @param {Object} data - Audit result data
|
* @param {Object} data - Audit result data
|
||||||
*/
|
*/
|
||||||
const handleAuditResult = (data) => {
|
const handleAuditResult = (data: AuditResultPayload) => {
|
||||||
if (!data.id || !data.json_report) return;
|
if (!data.id || !data.json_report) return;
|
||||||
|
|
||||||
const pendingAudit = pendingAudits.get(data.id);
|
const pendingAudit = pendingAudits.get(data.id);
|
||||||
@@ -130,13 +167,14 @@ const handleAuditResult = (data) => {
|
|||||||
const { resolve } = pendingAudit;
|
const { resolve } = pendingAudit;
|
||||||
|
|
||||||
try {
|
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,
|
id: data.id,
|
||||||
|
modelId: `bonsai:${data.id}`,
|
||||||
date: new Date().toISOString(),
|
date: new Date().toISOString(),
|
||||||
modelName: 'Bonsai IFC Model',
|
modelName: 'Bonsai IFC Model',
|
||||||
document: IDS.Module.activeDocument,
|
document: IDS.Module.activeDocument ?? "",
|
||||||
data: reportData,
|
data: reportData,
|
||||||
htmlReport: data.html_report
|
htmlReport: data.html_report
|
||||||
};
|
};
|
||||||
@@ -152,7 +190,8 @@ const handleAuditResult = (data) => {
|
|||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Bonsai.auditing = false;
|
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);
|
resolve(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -161,7 +200,7 @@ const handleAuditResult = (data) => {
|
|||||||
* Handles audit errors from Bonsai server
|
* Handles audit errors from Bonsai server
|
||||||
* @param {Object} data - Error data
|
* @param {Object} data - Error data
|
||||||
*/
|
*/
|
||||||
const handleAuditError = (data) => {
|
const handleAuditError = (data: AuditErrorPayload) => {
|
||||||
if (!data.id) return;
|
if (!data.id) return;
|
||||||
|
|
||||||
const pendingAudit = pendingAudits.get(data.id);
|
const pendingAudit = pendingAudits.get(data.id);
|
||||||
@@ -174,7 +213,6 @@ const handleAuditError = (data) => {
|
|||||||
const { resolve } = pendingAudit;
|
const { resolve } = pendingAudit;
|
||||||
|
|
||||||
Bonsai.auditing = false;
|
Bonsai.auditing = false;
|
||||||
error(`Audit failed (Bonsai): ${data.error}`);
|
error(`Audit failed (Bonsai): ${data.error ?? "Unknown error"}`);
|
||||||
resolve(null);
|
resolve(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
+129
-90
@@ -1,10 +1,18 @@
|
|||||||
import wasm from "$src/modules/wasm";
|
import wasm from "$src/modules/wasm";
|
||||||
import { clearIdsAuditReports } from "./api.svelte.js";
|
import { clearIdsAuditReports } from "./api.svelte";
|
||||||
import hyperid from "hyperid";
|
import hyperid from "hyperid";
|
||||||
import {tick} from "svelte";
|
import {tick} from "svelte";
|
||||||
|
import type { DocumentState, Facet, FacetValue, IdsDocument, IdsCardinality, Restriction, Specification } from "$src/types/ids";
|
||||||
|
|
||||||
export let Module = $state({
|
type ModuleState = {
|
||||||
documents: [],
|
documents: Record<string, IdsDocument>;
|
||||||
|
activeDocument: string | null;
|
||||||
|
status: "loading" | "ready" | "error";
|
||||||
|
states: Record<string, DocumentState>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Module: ModuleState = $state({
|
||||||
|
documents: {},
|
||||||
activeDocument: null,
|
activeDocument: null,
|
||||||
status: "loading",
|
status: "loading",
|
||||||
states: {}
|
states: {}
|
||||||
@@ -17,14 +25,15 @@ wasm.init().then(() => {
|
|||||||
Module.status = "error";
|
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]) {
|
if (!Module.states[docId]) {
|
||||||
Module.states[docId] = {
|
Module.states[docId] = {
|
||||||
activeTab: 'info',
|
activeTab: 'info',
|
||||||
viewMode: 'editor',
|
viewMode: 'editor',
|
||||||
activeSpecification: null
|
activeSpecification: null,
|
||||||
|
auditReport: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Object.assign(Module.states[docId], updates);
|
Object.assign(Module.states[docId], updates);
|
||||||
@@ -32,7 +41,7 @@ export function setDocumentState(docId, updates) {
|
|||||||
|
|
||||||
export async function createDocument() {
|
export async function createDocument() {
|
||||||
const docId = id();
|
const docId = id();
|
||||||
const doc = await wasm.createIDS();
|
const doc = await wasm.createIDS() as IdsDocument;
|
||||||
|
|
||||||
Module.documents[docId] = doc;
|
Module.documents[docId] = doc;
|
||||||
|
|
||||||
@@ -43,14 +52,14 @@ export async function createDocument() {
|
|||||||
Module.activeDocument = docId;
|
Module.activeDocument = docId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteDocument(id) {
|
export async function deleteDocument(id: string) {
|
||||||
// Clear any audit reports generated using this IDS document
|
// Clear any audit reports generated using this IDS document
|
||||||
clearIdsAuditReports(id);
|
clearIdsAuditReports(id);
|
||||||
|
|
||||||
delete Module.documents[id];
|
delete Module.documents[id];
|
||||||
delete Module.states[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 there are other documents, set the first one as active
|
||||||
if (Object.keys(Module.documents).length > 0) {
|
if (Object.keys(Module.documents).length > 0) {
|
||||||
Module.activeDocument = Object.keys(Module.documents)[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
|
// 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.
|
// 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 (typeof obj !== 'object' || obj === null) return obj;
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.map(normalizeIdsDict);
|
return obj.map(normalizeIdsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {};
|
const result: Record<string, unknown> = {};
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (key === 'xs:restriction' && Array.isArray(value) && value.length > 0) {
|
if (key === 'xs:restriction' && Array.isArray(value) && value.length > 0) {
|
||||||
// Convert xs:restriction array to restriction object
|
// Convert xs:restriction array to restriction object
|
||||||
const restriction = value[0];
|
const restriction = value[0] as Record<string, unknown>;
|
||||||
const newRestriction = {};
|
const newRestriction: Record<string, unknown> = {};
|
||||||
|
|
||||||
for (const [restrictionKey, restrictionValue] of Object.entries(restriction)) {
|
for (const [restrictionKey, restrictionValue] of Object.entries(restriction)) {
|
||||||
if (restrictionKey.startsWith('xs:')) {
|
if (restrictionKey.startsWith('xs:')) {
|
||||||
@@ -86,7 +95,7 @@ function normalizeIdsDict(obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result['restriction'] = newRestriction;
|
result.restriction = newRestriction;
|
||||||
} else {
|
} else {
|
||||||
result[key] = normalizeIdsDict(value);
|
result[key] = normalizeIdsDict(value);
|
||||||
}
|
}
|
||||||
@@ -96,13 +105,16 @@ function normalizeIdsDict(obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function openDocument() {
|
export async function openDocument() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const fileInput = document.createElement('input');
|
const fileInput = document.createElement('input') as HTMLInputElement & {
|
||||||
|
oncancel?: ((this: HTMLInputElement, ev: Event) => void) | null;
|
||||||
|
};
|
||||||
fileInput.type = 'file';
|
fileInput.type = 'file';
|
||||||
fileInput.accept = '.ids,.xml';
|
fileInput.accept = '.ids,.xml';
|
||||||
|
|
||||||
fileInput.onchange = async (event) => {
|
fileInput.onchange = async (event) => {
|
||||||
const file = event.target.files[0];
|
const target = event.target as HTMLInputElement | null;
|
||||||
|
const file = target?.files?.[0];
|
||||||
if (!file) {
|
if (!file) {
|
||||||
reject(new Error('No file selected'));
|
reject(new Error('No file selected'));
|
||||||
return;
|
return;
|
||||||
@@ -112,8 +124,8 @@ export async function openDocument() {
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = async (e) => {
|
reader.onload = async (e) => {
|
||||||
try {
|
try {
|
||||||
const fileContent = e.target.result;
|
const fileContent = (e.target as FileReader).result;
|
||||||
const doc = normalizeIdsDict(await wasm.openIDS(fileContent, false));
|
const doc = normalizeIdsDict(await wasm.openIDS(String(fileContent), false)) as IdsDocument;
|
||||||
const docId = id();
|
const docId = id();
|
||||||
|
|
||||||
// Add document to list and set as active
|
// 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;
|
if (!Module.activeDocument) return null;
|
||||||
|
|
||||||
const doc = $state.snapshot(Module.documents[Module.activeDocument]);
|
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;
|
return xmlString;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportDocument(docId) {
|
export async function exportDocument(docId: string) {
|
||||||
const doc = $state.snapshot(Module.documents[docId]);
|
const doc = $state.snapshot(Module.documents[docId]);
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
@@ -162,37 +174,38 @@ export async function exportDocument(docId) {
|
|||||||
throw new Error("Please create at least one specification before exporting the document.");
|
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
|
// Create and download file
|
||||||
const blob = new Blob([xmlString], { type: 'application/xml' });
|
const blob = new Blob([xmlString], { type: 'application/xml' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
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();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createSpecification(docId) {
|
export async function createSpecification(docId: string) {
|
||||||
const spec = await wasm.createSpecification();
|
const spec = await wasm.createSpecification() as Specification;
|
||||||
|
|
||||||
// Add specification to document
|
// Add specification to document
|
||||||
Module.documents[docId].specifications.specification.push(spec);
|
Module.documents[docId].specifications.specification.push(spec);
|
||||||
|
|
||||||
// Set as active specification
|
// Set as active specification
|
||||||
if (Module.activeDocument == docId) {
|
if (Module.activeDocument === docId) {
|
||||||
const state = Module.states[docId];
|
const state = Module.states[docId];
|
||||||
state.activeSpecification = Module.documents[docId].specifications.specification.length - 1;
|
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);
|
Module.documents[docId].specifications.specification.splice(specId, 1);
|
||||||
|
|
||||||
if (Module.activeDocument == docId) {
|
if (Module.activeDocument === docId) {
|
||||||
const state = Module.states[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
|
// We need to wait for the next tick here because of Svelte's internal shenanigans
|
||||||
await tick();
|
await tick();
|
||||||
setDocumentState(docId, { activeSpecification: null });
|
setDocumentState(docId, { activeSpecification: null });
|
||||||
@@ -209,124 +222,148 @@ export async function deleteSpecification(docId, specId) {
|
|||||||
* clause: "applicability", "requirements"
|
* clause: "applicability", "requirements"
|
||||||
* facet: "entity", "attribute", "classification", "partOf", "property", "material"
|
* facet: "entity", "attribute", "classification", "partOf", "property", "material"
|
||||||
*/
|
*/
|
||||||
export async function createFacet(docId, specId, clause, facet) {
|
export async function createFacet(
|
||||||
let facetObj;
|
docId: string,
|
||||||
if (facet == "entity") {
|
specId: number,
|
||||||
facetObj = await wasm.createEntityFacet(clause, {});
|
clause: "applicability" | "requirements",
|
||||||
} else if (facet == "attribute") {
|
facet: "entity" | "attribute" | "classification" | "partOf" | "property" | "material"
|
||||||
facetObj = await wasm.createAttributeFacet(clause, {});
|
) {
|
||||||
} else if (facet == "classification") {
|
let facetObj: Facet | undefined;
|
||||||
facetObj = await wasm.createClassificationFacet(clause, {});
|
if (facet === "entity") {
|
||||||
} else if (facet == "partOf") {
|
facetObj = await wasm.createEntityFacet(clause, {}) as Facet;
|
||||||
facetObj = await wasm.createPartOfFacet(clause, {});
|
} else if (facet === "attribute") {
|
||||||
} else if (facet == "property") {
|
facetObj = await wasm.createAttributeFacet(clause, {}) as Facet;
|
||||||
facetObj = await wasm.createPropertyFacet(clause, {});
|
} else if (facet === "classification") {
|
||||||
} else if (facet == "material") {
|
facetObj = await wasm.createClassificationFacet(clause, {}) as Facet;
|
||||||
facetObj = await wasm.createMaterialFacet(clause, {});
|
} 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])) {
|
if (!facetObj) return;
|
||||||
Module.documents[docId].specifications.specification[specId][clause][facet] = [];
|
|
||||||
|
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) {
|
export async function deleteFacet(
|
||||||
delete Module.documents[docId].specifications.specification[specId][clause][facet][facetId];
|
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;
|
||||||
|
list.splice(facetId, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSpecUsage(spec) {
|
export function getSpecUsage(spec?: Specification | null): IdsCardinality {
|
||||||
if (!spec?.applicability) return 'required';
|
if (!spec?.applicability) return 'required';
|
||||||
const minOccurs = spec.applicability["@minOccurs"];
|
const minOccurs = spec.applicability["@minOccurs"] as number | undefined;
|
||||||
const maxOccurs = spec.applicability["@maxOccurs"];
|
const maxOccurs = spec.applicability["@maxOccurs"] as number | "unbounded" | undefined;
|
||||||
|
|
||||||
if (minOccurs === 1 && maxOccurs === "unbounded") return 'required';
|
if (minOccurs !== 0) return 'required';
|
||||||
if (minOccurs === 0 && maxOccurs === "unbounded") return 'optional';
|
if (minOccurs === 0 && maxOccurs !== 0) return 'optional';
|
||||||
if (minOccurs === 0 && maxOccurs === 0) return 'prohibited';
|
if (maxOccurs === 0) return 'prohibited';
|
||||||
return 'required';
|
return 'required';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Converts facet to human-readable description
|
// 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 "";
|
if (!facet) return "";
|
||||||
|
|
||||||
const usage = getSpecUsage(spec);
|
const usage = getSpecUsage(spec);
|
||||||
const descriptions = [];
|
const descriptions: string[] = [];
|
||||||
|
|
||||||
// Entity facet
|
// Entity facet
|
||||||
if (facetType === "entity") {
|
if (facetType === "entity") {
|
||||||
if (clauseType === "applicability") {
|
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 {
|
} 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) {
|
if (facet.predefinedType) {
|
||||||
descriptions.push(`and type ${stringifyValue(facet.predefinedType)}`);
|
descriptions.push(`and type ${stringifyValue(facet.predefinedType as FacetValue)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attribute facet
|
// Attribute facet
|
||||||
else if (facetType === "attribute") {
|
else if (facetType === "attribute") {
|
||||||
if (clauseType === "applicability") {
|
if (clauseType === "applicability") {
|
||||||
descriptions.push(`All data where attribute ${stringifyValue(facet.name)}`);
|
descriptions.push(`All data where attribute ${stringifyValue(facet.name as FacetValue)}`);
|
||||||
} else {
|
} 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
|
// Property facet
|
||||||
else if (facetType === "property") {
|
else if (facetType === "property") {
|
||||||
if (clauseType === "applicability") {
|
if (clauseType === "applicability") {
|
||||||
descriptions.push(`Elements where property ${stringifyValue(facet.baseName)}`);
|
descriptions.push(`Elements where property ${stringifyValue(facet.baseName as FacetValue)}`);
|
||||||
} else {
|
} 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) {
|
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
|
// Classification facet
|
||||||
else if (facetType === "classification") {
|
else if (facetType === "classification") {
|
||||||
if (clauseType === "applicability") {
|
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 {
|
} 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) {
|
if (facet.value) {
|
||||||
descriptions.push(`and classification ${stringifyValue(facet.value)}`);
|
descriptions.push(`and classification ${stringifyValue(facet.value as FacetValue)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Material facet
|
// Material facet
|
||||||
else if (facetType === "material") {
|
else if (facetType === "material") {
|
||||||
if (clauseType === "applicability") {
|
if (clauseType === "applicability") {
|
||||||
descriptions.push(`All data where material ${stringifyValue(facet.value)}`);
|
descriptions.push(`All data where material ${stringifyValue(facet.value as FacetValue)}`);
|
||||||
} else {
|
} 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
|
// PartOf facet
|
||||||
else if (facetType === "partOf") {
|
else if (facetType === "partOf") {
|
||||||
if (clauseType === "applicability") {
|
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) {
|
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 {
|
} 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) {
|
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) {
|
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
|
// Post-process for prohibited and optional requirements
|
||||||
let isProhibited = false;
|
let isProhibited = false;
|
||||||
|
|
||||||
if (usage == "prohibited") isProhibited = !isProhibited;
|
if (usage === "prohibited") isProhibited = !isProhibited;
|
||||||
if (clauseType == "requirements" && "@cardinality" in facet && facet["@cardinality"] == "prohibited") isProhibited = !isProhibited;
|
if (clauseType === "requirements" && "@cardinality" in facet && facet["@cardinality"] === "prohibited") {
|
||||||
|
isProhibited = !isProhibited;
|
||||||
|
}
|
||||||
|
|
||||||
if (isProhibited)
|
if (isProhibited)
|
||||||
combined = combined.replace("Shall", "Shall not").replace("shall", "shall not");
|
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");
|
combined = combined.replace("Shall", "May").replace("shall", "may");
|
||||||
|
|
||||||
return renderFacetString(combined);
|
return renderFacetString(combined);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts value objects to human-readable strings
|
// Converts value objects to human-readable strings
|
||||||
function stringifyValue(value) {
|
function stringifyValue(value?: FacetValue) {
|
||||||
if (!value) return "is provided";
|
if (!value) return "is provided";
|
||||||
if (value.simpleValue) return `is **${value.simpleValue}**`;
|
if (value.simpleValue) return `is **${value.simpleValue}**`;
|
||||||
if (value.restriction) return stringifyRestriction(value.restriction);
|
if (value.restriction) return stringifyRestriction(value.restriction);
|
||||||
@@ -357,7 +396,7 @@ function stringifyValue(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Converts restriction objects to human-readable strings
|
// Converts restriction objects to human-readable strings
|
||||||
function stringifyRestriction(restriction) {
|
function stringifyRestriction(restriction: Restriction) {
|
||||||
if (!restriction) return "";
|
if (!restriction) return "";
|
||||||
|
|
||||||
// Handle enumeration
|
// Handle enumeration
|
||||||
@@ -394,7 +433,7 @@ function stringifyRestriction(restriction) {
|
|||||||
if (restriction.maxExclusive && restriction.maxExclusive.length > 0) {
|
if (restriction.maxExclusive && restriction.maxExclusive.length > 0) {
|
||||||
parts.push(`**< ${restriction.maxExclusive[0]['@value'] || ''}**`);
|
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
|
// Handle length range restrictions
|
||||||
@@ -406,18 +445,18 @@ function stringifyRestriction(restriction) {
|
|||||||
if (restriction.maxLength && restriction.maxLength.length > 0) {
|
if (restriction.maxLength && restriction.maxLength.length > 0) {
|
||||||
parts.push(`**max length ${restriction.maxLength[0]['@value'] || ''}**`);
|
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";
|
return "has complex restriction";
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFacetString(text) {
|
function renderFacetString(text: string): string {
|
||||||
// Convert **text** to <strong>text</strong>
|
// 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>
|
// 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
|
* Show an error toast notification
|
||||||
* @param {string} message - The error message to display
|
* @param {string} message - The error message to display
|
||||||
*/
|
*/
|
||||||
export function error(message) {
|
export function error(message: string): void {
|
||||||
toast.error(message);
|
toast.error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ export function error(message) {
|
|||||||
* Show a success toast notification
|
* Show a success toast notification
|
||||||
* @param {string} message - The success message to display
|
* @param {string} message - The success message to display
|
||||||
*/
|
*/
|
||||||
export function success(message) {
|
export function success(message: string): void {
|
||||||
toast.success(message);
|
toast.success(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export function success(message) {
|
|||||||
* Show an info toast notification
|
* Show an info toast notification
|
||||||
* @param {string} message - The info message to display
|
* @param {string} message - The info message to display
|
||||||
*/
|
*/
|
||||||
export function info(message) {
|
export function info(message: string): void {
|
||||||
toast.info(message);
|
toast.info(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export function info(message) {
|
|||||||
* Show a warning toast notification
|
* Show a warning toast notification
|
||||||
* @param {string} message - The warning message to display
|
* @param {string} message - The warning message to display
|
||||||
*/
|
*/
|
||||||
export function warning(message) {
|
export function warning(message: string): void {
|
||||||
toast.warning(message);
|
toast.warning(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export function warning(message) {
|
|||||||
* @param {string} message - The loading message to display
|
* @param {string} message - The loading message to display
|
||||||
* @returns {string} - Toast ID for dismissing later
|
* @returns {string} - Toast ID for dismissing later
|
||||||
*/
|
*/
|
||||||
export function loading(message) {
|
export function loading(message: string): string | number {
|
||||||
return toast.loading(message);
|
return toast.loading(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ export function loading(message) {
|
|||||||
* Dismiss a specific toast
|
* Dismiss a specific toast
|
||||||
* @param {string} toastId - The toast ID to dismiss
|
* @param {string} toastId - The toast ID to dismiss
|
||||||
*/
|
*/
|
||||||
export function dismiss(toastId) {
|
export function dismiss(toastId: string | number): void {
|
||||||
toast.dismiss(toastId);
|
toast.dismiss(toastId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +57,16 @@ export function dismiss(toastId) {
|
|||||||
* @param {string} messages.success - Success message
|
* @param {string} messages.success - Success message
|
||||||
* @param {string} messages.error - Error 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, {
|
return toast.promise(promiseToTrack, {
|
||||||
loading: messages.loading,
|
loading: messages.loading,
|
||||||
success: messages.success,
|
success: messages.success,
|
||||||
error: messages.error,
|
error: messages.error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
+53
-42
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import hyperid from "hyperid";
|
import hyperid from "hyperid";
|
||||||
import EventEmitter from "eventemitter3";
|
import EventEmitter from "eventemitter3";
|
||||||
|
import type { WorkerResponse } from "$src/types/wasm";
|
||||||
|
|
||||||
// Message types
|
// Message types
|
||||||
export const MessageType = {
|
export const MessageType = {
|
||||||
@@ -25,52 +26,57 @@ export const MessageType = {
|
|||||||
|
|
||||||
// WASM module disposed
|
// WASM module disposed
|
||||||
DISPOSED: 'disposed'
|
DISPOSED: 'disposed'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type PendingMessage = {
|
||||||
|
resolve: (value?: unknown) => void;
|
||||||
|
reject: (reason?: unknown) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type WasmReadyState = boolean | Promise<boolean>;
|
||||||
|
|
||||||
class WASMModule extends EventEmitter {
|
class WASMModule extends EventEmitter {
|
||||||
id = hyperid();
|
id = hyperid();
|
||||||
ready = false;
|
ready: WasmReadyState = false;
|
||||||
worker = null;
|
worker: Worker | null = null;
|
||||||
pendingMessages = new Map();
|
pendingMessages = new Map<string, PendingMessage>();
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
if (this.ready === true) return;
|
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._handleWorkerMessage(event.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.worker.onerror = (error) => {
|
this.worker.onerror = (error: ErrorEvent) => {
|
||||||
console.error('[WASM] Web worker error:', error);
|
console.error('[WASM] Web worker error:', error);
|
||||||
this._rejectPendingMessages(error);
|
this._rejectPendingMessages(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.ready = new Promise(async (resolve, reject) => {
|
this.ready = this._sendMessage(MessageType.INIT)
|
||||||
try {
|
.then(() => true)
|
||||||
await this._sendMessage(MessageType.INIT);
|
.catch((error) => {
|
||||||
resolve(true);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[WASM] Failed to initialize:', error);
|
console.error('[WASM] Failed to initialize:', error);
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
reject(error);
|
throw error;
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return this.ready;
|
return this.ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _sendMessage(type, payload = {}) {
|
async _sendMessage(type: string, payload: Record<string, unknown> = {}): Promise<unknown> {
|
||||||
if (!this.worker) throw new Error('Worker not initialized');
|
const worker = this.worker;
|
||||||
|
if (!worker) throw new Error('Worker not initialized');
|
||||||
|
|
||||||
const id = this.id();
|
const id = this.id();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.pendingMessages.set(id, { resolve, reject });
|
this.pendingMessages.set(id, { resolve, reject });
|
||||||
|
|
||||||
this.worker.postMessage({
|
worker.postMessage({
|
||||||
type,
|
type,
|
||||||
payload,
|
payload,
|
||||||
id
|
id
|
||||||
@@ -78,7 +84,7 @@ class WASMModule extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleWorkerMessage({ type, payload, id }) {
|
_handleWorkerMessage({ type, payload, id }: WorkerResponse) {
|
||||||
const pendingMessage = this.pendingMessages.get(id);
|
const pendingMessage = this.pendingMessages.get(id);
|
||||||
|
|
||||||
if (!pendingMessage) {
|
if (!pendingMessage) {
|
||||||
@@ -97,23 +103,28 @@ class WASMModule extends EventEmitter {
|
|||||||
case MessageType.API_RESPONSE:
|
case MessageType.API_RESPONSE:
|
||||||
resolve(payload);
|
resolve(payload);
|
||||||
break;
|
break;
|
||||||
case MessageType.ERROR:
|
case MessageType.ERROR: {
|
||||||
reject(new Error(payload.message));
|
const message =
|
||||||
|
payload && typeof payload === "object" && "message" in payload
|
||||||
|
? String(payload.message)
|
||||||
|
: "Unknown worker error";
|
||||||
|
reject(new Error(message));
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.warn('[WASM] Unknown message type:', type);
|
console.warn('[WASM] Unknown message type:', type);
|
||||||
reject(new Error(`Unknown message type: ${type}`));
|
reject(new Error(`Unknown message type: ${type}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_rejectPendingMessages(error) {
|
_rejectPendingMessages(error: unknown) {
|
||||||
for (const { reject } of this.pendingMessages.values()) {
|
for (const { reject } of this.pendingMessages.values()) {
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
this.pendingMessages.clear();
|
this.pendingMessages.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _apiCall(method, ...args) {
|
async _apiCall(method: string, ...args: unknown[]) {
|
||||||
if (!this.ready) await this.init();
|
if (!this.ready) await this.init();
|
||||||
|
|
||||||
const result = await this._sendMessage(MessageType.API_CALL, { method, args });
|
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
|
* Get all entity classes in a given IFC schema
|
||||||
*/
|
*/
|
||||||
async getAllEntityClasses(schema) {
|
async getAllEntityClasses(schema: string) {
|
||||||
return this._apiCall('getAllEntityClasses', schema);
|
return this._apiCall('getAllEntityClasses', schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all data types in a given IFC schema
|
* Get all data types in a given IFC schema
|
||||||
*/
|
*/
|
||||||
async getAllDataTypes(schema) {
|
async getAllDataTypes(schema: string) {
|
||||||
return this._apiCall('getAllDataTypes', schema);
|
return this._apiCall('getAllDataTypes', schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get predefined types for a given IFC entity
|
* Get predefined types for a given IFC entity
|
||||||
*/
|
*/
|
||||||
async getPredefinedTypes(schema, entity) {
|
async getPredefinedTypes(schema: string, entity: string) {
|
||||||
return this._apiCall('getPredefinedTypes', schema, entity);
|
return this._apiCall('getPredefinedTypes', schema, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all attributes for a given IFC 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);
|
return this._apiCall('getEntityAttributes', schema, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get applicable property sets for a given IFC 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);
|
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.
|
* 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);
|
return this._apiCall('loadIfc', ifcData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unload an IFC file
|
* Unload an IFC file
|
||||||
*/
|
*/
|
||||||
async unloadIfc(ifcId) {
|
async unloadIfc(ifcId: string) {
|
||||||
return this._apiCall('unloadIfc', ifcId);
|
return this._apiCall('unloadIfc', ifcId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audit a loaded IFC file against IDS specifications
|
* 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;
|
const idsBytes = idsData instanceof ArrayBuffer ? new Uint8Array(idsData) : idsData;
|
||||||
|
|
||||||
return this._apiCall('auditIfc', ifcId, Array.from(idsBytes));
|
return this._apiCall('auditIfc', ifcId, Array.from(idsBytes));
|
||||||
@@ -204,70 +215,70 @@ class WASMModule extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Open an existing IDS from XML string
|
* Open an existing IDS from XML string
|
||||||
*/
|
*/
|
||||||
async openIDS(idsXml, validate = false) {
|
async openIDS(idsXml: string, validate = false) {
|
||||||
return this._apiCall('openIDS', idsXml, validate);
|
return this._apiCall('openIDS', idsXml, validate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a specification
|
* Create a specification
|
||||||
*/
|
*/
|
||||||
async createSpecification(options = {}) {
|
async createSpecification(options: Record<string, unknown> = {}) {
|
||||||
return this._apiCall('createSpecification', options);
|
return this._apiCall('createSpecification', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an entity facet
|
* Create an entity facet
|
||||||
*/
|
*/
|
||||||
async createEntityFacet(clause, options = {}) {
|
async createEntityFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||||
return this._apiCall('createEntityFacet', clause, options);
|
return this._apiCall('createEntityFacet', clause, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an attribute facet
|
* Create an attribute facet
|
||||||
*/
|
*/
|
||||||
async createAttributeFacet(clause, options = {}) {
|
async createAttributeFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||||
return this._apiCall('createAttributeFacet', clause, options);
|
return this._apiCall('createAttributeFacet', clause, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a property facet
|
* Create a property facet
|
||||||
*/
|
*/
|
||||||
async createPropertyFacet(clause, options = {}) {
|
async createPropertyFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||||
return this._apiCall('createPropertyFacet', clause, options);
|
return this._apiCall('createPropertyFacet', clause, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a material facet
|
* Create a material facet
|
||||||
*/
|
*/
|
||||||
async createMaterialFacet(clause, options = {}) {
|
async createMaterialFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||||
return this._apiCall('createMaterialFacet', clause, options);
|
return this._apiCall('createMaterialFacet', clause, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a classification facet
|
* Create a classification facet
|
||||||
*/
|
*/
|
||||||
async createClassificationFacet(clause, options = {}) {
|
async createClassificationFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||||
return this._apiCall('createClassificationFacet', clause, options);
|
return this._apiCall('createClassificationFacet', clause, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a part-of facet
|
* Create a part-of facet
|
||||||
*/
|
*/
|
||||||
async createPartOfFacet(clause, options = {}) {
|
async createPartOfFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||||
return this._apiCall('createPartOfFacet', clause, options);
|
return this._apiCall('createPartOfFacet', clause, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate an IDS object
|
* Validate an IDS object
|
||||||
*/
|
*/
|
||||||
async validateIDS(idsObj) {
|
async validateIDS(idsObj: Record<string, unknown>) {
|
||||||
return await this._apiCall('validateIDS', idsObj);
|
return await this._apiCall('validateIDS', idsObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export IDS instance to XML string
|
* Export IDS instance to XML string
|
||||||
*/
|
*/
|
||||||
async exportIDS(idsObj) {
|
async exportIDS(idsObj: Record<string, unknown>) {
|
||||||
return this._apiCall('exportIDS', idsObj);
|
return this._apiCall('exportIDS', idsObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,4 +330,4 @@ export const {
|
|||||||
dispose
|
dispose
|
||||||
} = wasm;
|
} = wasm;
|
||||||
|
|
||||||
export default wasm;
|
export default wasm;
|
||||||
+18
-17
@@ -1,12 +1,13 @@
|
|||||||
import config from '../../../config.json';
|
|
||||||
import hyperid from 'hyperid';
|
import hyperid from 'hyperid';
|
||||||
|
import config from '../../../config.json';
|
||||||
|
import type { AuditReportData } from "$src/types/report";
|
||||||
|
|
||||||
let pyodide = null;
|
let pyodide: any = null;
|
||||||
let id = hyperid();
|
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;
|
pyodide = pdide;
|
||||||
|
|
||||||
// Load Python API bindings
|
// 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(`
|
const result = await pyodide.runPythonAsync(`
|
||||||
from api import get_predefined_types_for_entity
|
from api import get_predefined_types_for_entity
|
||||||
predef_types = get_predefined_types_for_entity("${schema}", "${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 });
|
return result.toJs({ dict_converter: Object.fromEntries });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllEntityClasses(schema) {
|
export async function getAllEntityClasses(schema: string) {
|
||||||
const result = await pyodide.runPythonAsync(`
|
const result = await pyodide.runPythonAsync(`
|
||||||
from api import get_all_entity_classes
|
from api import get_all_entity_classes
|
||||||
entities = get_all_entity_classes("${schema}")
|
entities = get_all_entity_classes("${schema}")
|
||||||
@@ -36,7 +37,7 @@ export async function getAllEntityClasses(schema) {
|
|||||||
return result.toJs({ dict_converter: Object.fromEntries });
|
return result.toJs({ dict_converter: Object.fromEntries });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllDataTypes(schema) {
|
export async function getAllDataTypes(schema: string) {
|
||||||
const result = await pyodide.runPythonAsync(`
|
const result = await pyodide.runPythonAsync(`
|
||||||
from api import get_all_data_types
|
from api import get_all_data_types
|
||||||
data_types = get_all_data_types("${schema}")
|
data_types = get_all_data_types("${schema}")
|
||||||
@@ -45,7 +46,7 @@ export async function getAllDataTypes(schema) {
|
|||||||
return result.toJs({ dict_converter: Object.fromEntries });
|
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(`
|
const result = await pyodide.runPythonAsync(`
|
||||||
from api import get_entity_attributes
|
from api import get_entity_attributes
|
||||||
attrs = get_entity_attributes("${schema}", "${entity}")
|
attrs = get_entity_attributes("${schema}", "${entity}")
|
||||||
@@ -54,7 +55,7 @@ export async function getEntityAttributes(schema, entity) {
|
|||||||
return result.toJs({ dict_converter: Object.fromEntries });
|
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(`
|
const result = await pyodide.runPythonAsync(`
|
||||||
from api import get_applicable_psets
|
from api import get_applicable_psets
|
||||||
psets = get_applicable_psets("${schema}", "${entity}", "${predefinedType}")
|
psets = get_applicable_psets("${schema}", "${entity}", "${predefinedType}")
|
||||||
@@ -81,7 +82,7 @@ export async function getStandardClassificationSystems() {
|
|||||||
return result.toJs({ dict_converter: Object.fromEntries });
|
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 ifc_id = id();
|
||||||
const path = `/tmp/${encodeURIComponent(ifc_id)}.ifc`;
|
const path = `/tmp/${encodeURIComponent(ifc_id)}.ifc`;
|
||||||
|
|
||||||
@@ -97,14 +98,14 @@ export async function loadIfc(ifcData) {
|
|||||||
return ifc_id;
|
return ifc_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unloadIfc(ifcId) {
|
export async function unloadIfc(ifcId: string) {
|
||||||
const path = `/tmp/${encodeURIComponent(ifcId)}.ifc`;
|
const path = `/tmp/${encodeURIComponent(ifcId)}.ifc`;
|
||||||
|
|
||||||
pyodide.FS.unlink(path);
|
pyodide.FS.unlink(path);
|
||||||
LoadedIFC.delete(ifcId);
|
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 reporter = pyodide.pyimport("ifctester.reporter");
|
||||||
const api = pyodide.pyimport("api");
|
const api = pyodide.pyimport("api");
|
||||||
|
|
||||||
@@ -116,16 +117,16 @@ export async function auditIfc(ifcId, idsData) {
|
|||||||
specs.validate(ifc);
|
specs.validate(ifc);
|
||||||
|
|
||||||
// Create report in both HTML and JSON formats
|
// Create report in both HTML and JSON formats
|
||||||
let jsonReporter = reporter.Json(specs);
|
const jsonReporter = reporter.Json(specs);
|
||||||
jsonReporter.report();
|
jsonReporter.report();
|
||||||
const jsonReport = jsonReporter.to_string();
|
const jsonReport = jsonReporter.to_string();
|
||||||
|
|
||||||
let htmlReporter = reporter.Html(specs);
|
const htmlReporter = reporter.Html(specs);
|
||||||
htmlReporter.report();
|
htmlReporter.report();
|
||||||
const htmlReport = htmlReporter.to_string();
|
const htmlReport = htmlReporter.to_string();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
json: JSON.parse(jsonReport),
|
json: JSON.parse(jsonReport) as AuditReportData,
|
||||||
html: htmlReport
|
html: htmlReport
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -142,4 +143,4 @@ export const API = {
|
|||||||
"loadIfc": loadIfc,
|
"loadIfc": loadIfc,
|
||||||
"unloadIfc": unloadIfc,
|
"unloadIfc": unloadIfc,
|
||||||
"auditIfc": auditIfc
|
"auditIfc": auditIfc
|
||||||
};
|
};
|
||||||
+98
-18
@@ -2,13 +2,19 @@
|
|||||||
* IDS module
|
* IDS module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let pyodide = null;
|
let pyodide: any = null;
|
||||||
|
|
||||||
// IDS Python classes
|
// IDS Python classes
|
||||||
let Ids, Specification;
|
let Ids: any;
|
||||||
let Entity, Attribute, Property, Material, Classification, PartOf;
|
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;
|
pyodide = pdide;
|
||||||
|
|
||||||
await pyodide.loadPackagesFromImports(`
|
await pyodide.loadPackagesFromImports(`
|
||||||
@@ -29,24 +35,24 @@ export async function init(pdide) {
|
|||||||
PartOf = pyodide.pyimport("ifctester.facet").PartOf;
|
PartOf = pyodide.pyimport("ifctester.facet").PartOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _idsToInstance(idsObj) {
|
function _idsToInstance(idsObj: Record<string, unknown>) {
|
||||||
const ids_raw = Ids();
|
const ids_raw = Ids();
|
||||||
return ids_raw.parse(pyodide.toPy(idsObj))
|
return ids_raw.parse(pyodide.toPy(idsObj))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createIDS() {
|
export function createIDS(): Record<string, unknown> {
|
||||||
const ids_raw = Ids()
|
const ids_raw = Ids()
|
||||||
return ids_raw.asdict().toJs({dict_converter: Object.fromEntries});
|
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_from_xml_string = pyodide.pyimport("api").ids_from_xml_string;
|
||||||
const ids_raw = ids_from_xml_string(ids_xml, validate);
|
const ids_raw = ids_from_xml_string(ids_xml, validate);
|
||||||
|
|
||||||
return ids_raw.asdict().toJs({dict_converter: Object.fromEntries});
|
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 ids_raw = _idsToInstance(idsObj)
|
||||||
const tempFilename = `temp_${Date.now()}.xml`;
|
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
|
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;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exportIDS(idsObj) {
|
export function exportIDS(idsObj: Record<string, unknown>): string {
|
||||||
const ids_raw = _idsToInstance(idsObj)
|
const ids_raw = _idsToInstance(idsObj)
|
||||||
return ids_raw.to_string();
|
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({
|
const spec = Specification.callKwargs({
|
||||||
name: name,
|
name: name,
|
||||||
ifcVersion: ifcVersion,
|
ifcVersion: ifcVersion,
|
||||||
@@ -79,7 +99,14 @@ export function createSpecification({name = "Unnamed", ifcVersion = ["IFC2X3", "
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @instructions
|
// @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({
|
const entity = Entity.callKwargs({
|
||||||
name: name,
|
name: name,
|
||||||
predefinedType: predefinedType,
|
predefinedType: predefinedType,
|
||||||
@@ -89,7 +116,15 @@ export function createEntityFacet(clause, {name = "IFCWALL", predefinedType = nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @cardinality, @instructions
|
// @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({
|
const attribute = Attribute.callKwargs({
|
||||||
name: name,
|
name: name,
|
||||||
value: value,
|
value: value,
|
||||||
@@ -100,7 +135,16 @@ export function createAttributeFacet(clause, {name = "Name", value = null, cardi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @uri, @cardinality, @instructions
|
// @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({
|
const classification = Classification.callKwargs({
|
||||||
value: value,
|
value: value,
|
||||||
system: system,
|
system: system,
|
||||||
@@ -112,7 +156,16 @@ export function createClassificationFacet(clause, {value = null, system = null,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @relation, @cardinality, @instructions
|
// @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({
|
const part_of = PartOf.callKwargs({
|
||||||
name: name,
|
name: name,
|
||||||
predefinedType: predefinedType,
|
predefinedType: predefinedType,
|
||||||
@@ -124,7 +177,26 @@ export function createPartOfFacet(clause, {name = "IFCWALL", predefinedType = nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @dataType, @uri, @cardinality, @instructions
|
// @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({
|
const property = Property.callKwargs({
|
||||||
propertySet: propertySet,
|
propertySet: propertySet,
|
||||||
baseName: baseName,
|
baseName: baseName,
|
||||||
@@ -138,7 +210,15 @@ export function createPropertyFacet(clause, {propertySet = "Property_Set", baseN
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @uri, @cardinality, @instructions
|
// @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({
|
const material = Material.callKwargs({
|
||||||
value: value,
|
value: value,
|
||||||
uri: uri,
|
uri: uri,
|
||||||
@@ -149,7 +229,7 @@ export function createMaterialFacet(clause, {value = null, uri = null, cardinali
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to convert date to ISO format string
|
// 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;
|
if (!date) return null;
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
return d.toISOString().split('T')[0];
|
return d.toISOString().split('T')[0];
|
||||||
@@ -168,4 +248,4 @@ export const API = {
|
|||||||
"createPartOfFacet": createPartOfFacet,
|
"createPartOfFacet": createPartOfFacet,
|
||||||
"createPropertyFacet": createPropertyFacet,
|
"createPropertyFacet": createPropertyFacet,
|
||||||
"createMaterialFacet": createMaterialFacet,
|
"createMaterialFacet": createMaterialFacet,
|
||||||
};
|
};
|
||||||
+28
-15
@@ -4,13 +4,14 @@
|
|||||||
|
|
||||||
import { MessageType } from '../index';
|
import { MessageType } from '../index';
|
||||||
import config from '../../../config.json';
|
import config from '../../../config.json';
|
||||||
import * as IDS from './ids.js';
|
import * as IDS from './ids';
|
||||||
import * as API from './api';
|
import * as API from './api';
|
||||||
|
import type { ApiCallPayload, WorkerRequest } from "$src/types/wasm";
|
||||||
|
|
||||||
let pyodide = null;
|
let pyodide: any = null;
|
||||||
let ready = false;
|
let ready = false;
|
||||||
|
|
||||||
self.addEventListener('message', async (event) => {
|
self.addEventListener('message', async (event: MessageEvent<WorkerRequest>) => {
|
||||||
console.log("[worker] Received message:", event.data);
|
console.log("[worker] Received message:", event.data);
|
||||||
const { type, payload, id } = event.data;
|
const { type, payload, id } = event.data;
|
||||||
|
|
||||||
@@ -25,27 +26,33 @@ self.addEventListener('message', async (event) => {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType.API_CALL:
|
case MessageType.API_CALL: {
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
throw new Error('[worker] Pyodide not initialized');
|
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({
|
self.postMessage({
|
||||||
type: MessageType.API_RESPONSE,
|
type: MessageType.API_RESPONSE,
|
||||||
payload: result,
|
payload: result,
|
||||||
id
|
id
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`[worker] Unknown message type: ${type}`);
|
throw new Error(`[worker] Unknown message type: ${type}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
const stack = error instanceof Error ? error.stack : undefined;
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
payload: {
|
payload: {
|
||||||
message: error.message,
|
message,
|
||||||
stack: error.stack
|
stack
|
||||||
},
|
},
|
||||||
id
|
id
|
||||||
});
|
});
|
||||||
@@ -76,7 +83,13 @@ async function initEnvironment() {
|
|||||||
await pyodide.loadPackage("shapely");
|
await pyodide.loadPackage("shapely");
|
||||||
|
|
||||||
// Install IfcTester
|
// Install IfcTester
|
||||||
await micropip.install('ifctester');
|
const ifctesterManifest = await fetch('/worker/generated/ifctester.json').then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`[worker] Failed to load IfcTester wheel manifest: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.json() as Promise<{ wheel_url: string }>;
|
||||||
|
});
|
||||||
|
await micropip.install(ifctesterManifest.wheel_url);
|
||||||
|
|
||||||
// Initialize IDS and API
|
// Initialize IDS and API
|
||||||
await API.init(pyodide);
|
await API.init(pyodide);
|
||||||
@@ -93,17 +106,17 @@ async function cleanupEnvironment() {
|
|||||||
console.log("[worker] Closed environment");
|
console.log("[worker] Closed environment");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleApiCall({ method, args = [] }) {
|
async function handleApiCall({ method, args = [] }: ApiCallPayload) {
|
||||||
if (method === 'internal.cleanup') {
|
if (method === 'internal.cleanup') {
|
||||||
await cleanupEnvironment();
|
await cleanupEnvironment();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method in API.API) {
|
if (method in API.API) {
|
||||||
return await API.API[method](...args);
|
return await (API.API as Record<string, (...params: unknown[]) => unknown>)[method](...args);
|
||||||
} else if (method in IDS.API) {
|
|
||||||
return await IDS.API[method](...args);
|
|
||||||
} else {
|
|
||||||
throw new Error(`[worker] Unknown API method: ${method}`);
|
|
||||||
}
|
}
|
||||||
}
|
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>
|
<script lang="ts">
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
import * as IDS from "$src/modules/api/ids.svelte";
|
||||||
import FacetEditor from './FacetEditor.svelte';
|
import FacetEditor from './FacetEditor.svelte';
|
||||||
import CreateFacetDropdown from "$src/components/CreateFacetDropdown.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 activeDocument = $derived(
|
||||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
);
|
||||||
activeDocument.specifications.specification[documentState.activeSpecification] : 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) {
|
async function addFacet(facetType: FacetType) {
|
||||||
if (!activeSpecification) return;
|
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||||
|
|
||||||
await IDS.createFacet(
|
await IDS.createFacet(
|
||||||
IDS.Module.activeDocument,
|
IDS.Module.activeDocument,
|
||||||
documentState.activeSpecification,
|
documentState.activeSpecification ?? 0,
|
||||||
"applicability",
|
"applicability",
|
||||||
facetType
|
facetType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeFacet(facetType, facetIndex) {
|
async function removeFacet(facetType: FacetType, facetIndex: number) {
|
||||||
if (!activeSpecification) return;
|
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||||
|
|
||||||
await IDS.deleteFacet(
|
await IDS.deleteFacet(
|
||||||
IDS.Module.activeDocument,
|
IDS.Module.activeDocument,
|
||||||
documentState.activeSpecification,
|
documentState.activeSpecification ?? 0,
|
||||||
"applicability",
|
"applicability",
|
||||||
facetType,
|
facetType,
|
||||||
facetIndex
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="restrictions-panel">
|
<div class="restrictions-panel">
|
||||||
@@ -40,22 +59,19 @@
|
|||||||
<CreateFacetDropdown {addFacet} />
|
<CreateFacetDropdown {addFacet} />
|
||||||
</div>
|
</div>
|
||||||
<div class="restrictions-list">
|
<div class="restrictions-list">
|
||||||
{#if activeSpecification?.applicability}
|
{#if activeSpecification && applicabilityEntries.length > 0}
|
||||||
{#each Object.entries(activeSpecification.applicability) as [facetType, facets]}
|
{#each applicabilityEntries as [facetType, facets]}
|
||||||
{#if facetType !== "@minOccurs" && facetType !== "@maxOccurs"}
|
{#each facets as facet, index}
|
||||||
{#each facets as facet, index}
|
<FacetEditor
|
||||||
<FacetEditor
|
bind:facet={facets[index]}
|
||||||
bind:facet={facets[index]}
|
{facetType}
|
||||||
{facetType}
|
specification={activeSpecification}
|
||||||
specification={activeSpecification}
|
activeTab="applicability"
|
||||||
activeTab="applicability"
|
{removeFacet}
|
||||||
{removeFacet}
|
{index}
|
||||||
{index}
|
/>
|
||||||
key={`${activeDocument}-${documentState?.activeSpecification}-applicability-${facetType}-${index}`}
|
{/each}
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,40 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import RestrictionEditor from './RestrictionEditor.svelte';
|
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"
|
* Applicability facets wont have: "@uri", "@instructions", "@cardinality"
|
||||||
* @ in name --> simple string value
|
* @ in name --> simple string value
|
||||||
* else --> can be simpleValue, Restriction or list of Restrictions
|
* 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) => {
|
let {
|
||||||
return facet[prop] ?? "";
|
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) => {
|
const setSpecialProp = (prop: string, value: string) => {
|
||||||
facet[prop] = value;
|
(facet as Record<string, unknown>)[prop] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let baseId = $derived(`facet-${facetType}-${index}`);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="restriction-item">
|
<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="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" />
|
<RestrictionEditor bind:facet={facet} fieldName="predefinedType" label="Predefined Type" placeholder="e.g., SOLIDWALL" autocomplete="predefinedType" />
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Relation</label>
|
<label for={`${baseId}-relation`}>Relation</label>
|
||||||
<select class="form-input" bind:value={() => getSpecialProp("@relation"), (v) => setSpecialProp("@relation", v)}>
|
<select class="form-input" id={`${baseId}-relation`} bind:value={() => getSpecialProp("@relation"), (v) => setSpecialProp("@relation", v)}>
|
||||||
<option value="">Select relation...</option>
|
<option value="">Select relation...</option>
|
||||||
<option value="IFCRELAGGREGATES">IFCRELAGGREGATES</option>
|
<option value="IFCRELAGGREGATES">IFCRELAGGREGATES</option>
|
||||||
<option value="IFCRELASSIGNSTOGROUP">IFCRELASSIGNSTOGROUP</option>
|
<option value="IFCRELASSIGNSTOGROUP">IFCRELASSIGNSTOGROUP</option>
|
||||||
@@ -62,8 +82,8 @@
|
|||||||
{#if activeTab === 'requirements'}
|
{#if activeTab === 'requirements'}
|
||||||
{#if facetType !== 'entity'}
|
{#if facetType !== 'entity'}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Cardinality</label>
|
<label for={`${baseId}-cardinality`}>Cardinality</label>
|
||||||
<select class="form-input" bind:value={() => getSpecialProp("@cardinality"), (v) => setSpecialProp("@cardinality", v)}>
|
<select class="form-input" id={`${baseId}-cardinality`} bind:value={() => getSpecialProp("@cardinality"), (v) => setSpecialProp("@cardinality", v)}>
|
||||||
<option value="required">Required</option>
|
<option value="required">Required</option>
|
||||||
<option value="optional">Optional</option>
|
<option value="optional">Optional</option>
|
||||||
<option value="prohibited">Prohibited</option>
|
<option value="prohibited">Prohibited</option>
|
||||||
@@ -71,9 +91,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="form-group full-width">
|
<div class="form-group full-width">
|
||||||
<label>Instructions</label>
|
<label for={`${baseId}-instructions`}>Instructions</label>
|
||||||
<textarea class="form-input" bind:value={() => getSpecialProp("@instructions"), (v) => setSpecialProp("@instructions", v)} placeholder="Optional instructions for IFC authors" rows="2"></textarea>
|
<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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
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] ?? "";
|
return activeDocument?.info[prop] ?? "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const setProp = (prop, value) => {
|
const setProp = (prop: keyof IdsInfo, value: string) => {
|
||||||
|
if (!activeDocument) return;
|
||||||
activeDocument.info[prop] = value;
|
activeDocument.info[prop] = value;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -18,36 +22,36 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Title</label>
|
<label for="ids-title">Title</label>
|
||||||
<input class="form-input" type="text" bind:value={() => getProp("title"), (v) => setProp("title", v)} placeholder="Enter IDS title">
|
<input class="form-input" id="ids-title" type="text" bind:value={() => getProp("title"), (v) => setProp("title", v)} placeholder="Enter IDS title">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Author Email</label>
|
<label for="ids-author">Author Email</label>
|
||||||
<input class="form-input" type="email" bind:value={() => getProp("author"), (v) => setProp("author", v)} placeholder="Enter author">
|
<input class="form-input" id="ids-author" type="email" bind:value={() => getProp("author"), (v) => setProp("author", v)} placeholder="Enter author">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Version</label>
|
<label for="ids-version">Version</label>
|
||||||
<input class="form-input" type="text" bind:value={() => getProp("version"), (v) => setProp("version", v)} placeholder="Enter version">
|
<input class="form-input" id="ids-version" type="text" bind:value={() => getProp("version"), (v) => setProp("version", v)} placeholder="Enter version">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Date</label>
|
<label for="ids-date">Date</label>
|
||||||
<input class="form-input" type="date" bind:value={() => getProp("date"), (v) => setProp("date", v)}>
|
<input class="form-input" id="ids-date" type="date" bind:value={() => getProp("date"), (v) => setProp("date", v)}>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group full-width">
|
<div class="form-group full-width">
|
||||||
<label>Description</label>
|
<label for="ids-description">Description</label>
|
||||||
<textarea class="form-input" bind:value={() => getProp("description"), (v) => setProp("description", v)} placeholder="Enter description" rows="3"></textarea>
|
<textarea class="form-input" id="ids-description" bind:value={() => getProp("description"), (v) => setProp("description", v)} placeholder="Enter description" rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Purpose</label>
|
<label for="ids-purpose">Purpose</label>
|
||||||
<input class="form-input" type="text" bind:value={() => getProp("purpose"), (v) => setProp("purpose", v)} placeholder="Enter purpose">
|
<input class="form-input" id="ids-purpose" type="text" bind:value={() => getProp("purpose"), (v) => setProp("purpose", v)} placeholder="Enter purpose">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Milestone</label>
|
<label for="ids-milestone">Milestone</label>
|
||||||
<input class="form-input" type="text" bind:value={() => getProp("milestone"), (v) => setProp("milestone", v)} placeholder="Enter milestone">
|
<input class="form-input" id="ids-milestone" type="text" bind:value={() => getProp("milestone"), (v) => setProp("milestone", v)} placeholder="Enter milestone">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group full-width">
|
<div class="form-group full-width">
|
||||||
<label>Copyright</label>
|
<label for="ids-copyright">Copyright</label>
|
||||||
<input class="form-input" type="text" bind:value={() => getProp("copyright"), (v) => setProp("copyright", v)} placeholder="Enter copyright">
|
<input class="form-input" id="ids-copyright" type="text" bind:value={() => getProp("copyright"), (v) => setProp("copyright", v)} placeholder="Enter copyright">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
import * as IDS from "$src/modules/api/ids.svelte";
|
||||||
import { getAuditReportById, downloadAuditReport } from "$src/modules/api/api.svelte.js";
|
import { getAuditReportById, downloadAuditReport } from "$src/modules/api/api.svelte";
|
||||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||||
import * as Tooltip from "$src/lib/components/ui/tooltip";
|
import * as Tooltip from "$src/lib/components/ui/tooltip";
|
||||||
|
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||||
|
import type { DocumentState, Facet, IdsDocument, Specification } from "$src/types/ids";
|
||||||
|
|
||||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
let activeDocument = $derived(
|
||||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||||
let auditReport = $derived(documentState?.auditReport ? getAuditReportById(documentState.auditReport) : null);
|
);
|
||||||
let expandedSpecs = $state(new Set());
|
let documentState = $derived(
|
||||||
let expandedRequirements = $state(new Set());
|
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);
|
let allExpanded = $state(false);
|
||||||
|
|
||||||
|
type SpecificationStatus = boolean | 'skipped' | null;
|
||||||
|
|
||||||
// Open Editor mode and jump to a specific specification
|
// Open Editor mode and jump to a specific specification
|
||||||
function editSpecification(index) {
|
function editSpecification(index: number) {
|
||||||
if (IDS.Module.activeDocument) {
|
if (IDS.Module.activeDocument) {
|
||||||
IDS.setDocumentState(IDS.Module.activeDocument, {
|
IDS.setDocumentState(IDS.Module.activeDocument, {
|
||||||
viewMode: 'editor',
|
viewMode: 'editor',
|
||||||
@@ -23,13 +33,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSpecification(index) {
|
function toggleSpecification(index: number) {
|
||||||
if (expandedSpecs.has(index)) {
|
if (expandedSpecs.has(index)) {
|
||||||
expandedSpecs.delete(index);
|
expandedSpecs.delete(index);
|
||||||
} else {
|
} else {
|
||||||
expandedSpecs.add(index);
|
expandedSpecs.add(index);
|
||||||
}
|
}
|
||||||
expandedSpecs = new Set(expandedSpecs);
|
expandedSpecs = new Set<number>(expandedSpecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleAllSpecifications() {
|
function toggleAllSpecifications() {
|
||||||
@@ -37,7 +47,7 @@
|
|||||||
|
|
||||||
if (allExpanded) {
|
if (allExpanded) {
|
||||||
// Collapse all
|
// Collapse all
|
||||||
expandedSpecs = new Set();
|
expandedSpecs = new Set<number>();
|
||||||
allExpanded = false;
|
allExpanded = false;
|
||||||
} else {
|
} else {
|
||||||
// Expand all
|
// Expand all
|
||||||
@@ -47,26 +57,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpecificationStatus(specIndex, auditData) {
|
function getSpecificationStatus(specIndex: number, auditData: AuditReportData): SpecificationStatus {
|
||||||
const spec = auditData.specifications[specIndex];
|
const spec = auditData.specifications[specIndex];
|
||||||
if (!spec) return null;
|
if (!spec) return null;
|
||||||
return spec.is_skipped ? 'skipped' : spec.status;
|
return spec.is_skipped ? 'skipped' : spec.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpecificationStats(specIndex, auditData) {
|
function getSpecificationStats(specIndex: number, auditData: AuditReportData) {
|
||||||
const spec = auditData.specifications[specIndex];
|
const spec = auditData.specifications[specIndex];
|
||||||
if (!spec) return null;
|
if (!spec) return null;
|
||||||
return {
|
return {
|
||||||
requirements: `${spec.total_requirements || 0}`,
|
requirements: spec.total_requirements || 0,
|
||||||
requirementsPassed: `${spec.total_requirements_pass || 0}`,
|
requirementsPassed: spec.total_requirements_pass || 0,
|
||||||
checksTotal: `${spec.total_checks || 0}`,
|
checksTotal: spec.total_checks || 0,
|
||||||
checksPassed: `${spec.total_checks_pass || 0}`,
|
checksPassed: spec.total_checks_pass || 0,
|
||||||
applicableTotal: `${spec.total_applicable || 0}`,
|
applicableTotal: spec.total_applicable || 0,
|
||||||
applicablePassed: `${spec.total_applicable_pass || 0}`
|
applicablePassed: spec.total_applicable_pass || 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpecificationReason(specIndex, auditData) {
|
function getSpecificationReason(specIndex: number, auditData: AuditReportData) {
|
||||||
const spec = auditData.specifications[specIndex];
|
const spec = auditData.specifications[specIndex];
|
||||||
if (!spec) return null;
|
if (!spec) return null;
|
||||||
|
|
||||||
@@ -90,6 +100,10 @@
|
|||||||
return null; // No reason needed for passed specifications
|
return null; // No reason needed for passed specifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDocumentSpecificationUsage(spec: Specification) {
|
||||||
|
return IDS.getSpecUsage(spec);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleDownloadReport() {
|
async function handleDownloadReport() {
|
||||||
if (!auditReport) return;
|
if (!auditReport) return;
|
||||||
|
|
||||||
@@ -97,29 +111,62 @@
|
|||||||
await downloadAuditReport(auditReport.id);
|
await downloadAuditReport(auditReport.id);
|
||||||
success('Audit report downloaded successfully');
|
success('Audit report downloaded successfully');
|
||||||
} catch (err) {
|
} 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];
|
const spec = auditData.specifications[specIndex];
|
||||||
if (!spec || !spec.requirements || !spec.requirements[reqIndex]) return null;
|
if (!spec || !spec.requirements || !spec.requirements[reqIndex]) return null;
|
||||||
return spec.requirements[reqIndex];
|
return spec.requirements[reqIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleRequirementDetails(specIndex, reqIndex) {
|
type RequirementGroup = {
|
||||||
|
facetType: string;
|
||||||
|
items: { facet: Facet; reqIndex: number }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRequirementGroups(spec: Specification | undefined | null): RequirementGroup[] {
|
||||||
|
if (!spec?.requirements) return [];
|
||||||
|
|
||||||
|
const groups: RequirementGroup[] = [];
|
||||||
|
let reqIndex = 0;
|
||||||
|
|
||||||
|
for (const [facetType, facets] of Object.entries(spec.requirements)) {
|
||||||
|
if (!Array.isArray(facets) || facets.length === 0) continue;
|
||||||
|
groups.push({
|
||||||
|
facetType,
|
||||||
|
items: facets.map((facet) => ({
|
||||||
|
facet,
|
||||||
|
reqIndex: reqIndex++
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRequirementDetails(specIndex: number, reqIndex: number) {
|
||||||
const key = `${specIndex}-${reqIndex}`;
|
const key = `${specIndex}-${reqIndex}`;
|
||||||
if (expandedRequirements.has(key)) {
|
if (expandedRequirements.has(key)) {
|
||||||
expandedRequirements.delete(key);
|
expandedRequirements.delete(key);
|
||||||
} else {
|
} else {
|
||||||
expandedRequirements.add(key);
|
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}`);
|
return expandedRequirements.has(`${specIndex}-${reqIndex}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleActivation = (event: KeyboardEvent, action: () => void) => {
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
event.preventDefault();
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="ids-viewer">
|
<div class="ids-viewer">
|
||||||
@@ -207,8 +254,17 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#each activeDocument.specifications.specification as spec, index}
|
{#each activeDocument.specifications.specification as spec, index}
|
||||||
|
{@const usage = getDocumentSpecificationUsage(spec)}
|
||||||
|
{@const requirementGroups = getRequirementGroups(spec)}
|
||||||
<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="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-section">
|
||||||
<div class="spec-title-row">
|
<div class="spec-title-row">
|
||||||
<h2>{spec["@name"] || `Specification ${index + 1}`}</h2>
|
<h2>{spec["@name"] || `Specification ${index + 1}`}</h2>
|
||||||
@@ -237,19 +293,19 @@
|
|||||||
<p class="spec-description">{spec["@description"]}</p>
|
<p class="spec-description">{spec["@description"]}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="spec-stats">
|
<div class="spec-stats">
|
||||||
{#if spec.applicability["@minOccurs"] === 1 && spec.applicability["@maxOccurs"] === 'unbounded'}
|
{#if usage === 'required'}
|
||||||
<span class="stat-item">Required</span>
|
<span class="stat-item">Required</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if spec.applicability["@minOccurs"] === 0 && spec.applicability["@maxOccurs"] === 'unbounded'}
|
{#if usage === 'optional'}
|
||||||
<span class="stat-item">Optional</span>
|
<span class="stat-item">Optional</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if spec.applicability["@minOccurs"] === 0 && spec.applicability["@maxOccurs"] === 0}
|
{#if usage === 'prohibited'}
|
||||||
<span class="stat-item">Prohibited</span>
|
<span class="stat-item">Prohibited</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if auditReport}
|
{#if auditReport}
|
||||||
{@const stats = getSpecificationStats(index, auditReport.data)}
|
{@const stats = getSpecificationStats(index, auditReport.data)}
|
||||||
{@const status = getSpecificationStatus(index, auditReport.data)}
|
{@const status = getSpecificationStatus(index, auditReport.data)}
|
||||||
{#if stats && spec.applicability["@maxOccurs"] !== 0 && status !== 'skipped'}
|
{#if stats && usage !== 'prohibited' && status !== 'skipped'}
|
||||||
<span class="stat-item">Checks: {stats.checksPassed}/{stats.checksTotal}</span>
|
<span class="stat-item">Checks: {stats.checksPassed}/{stats.checksTotal}</span>
|
||||||
<span class="stat-item">Requirements: {stats.requirementsPassed}/{stats.requirements}</span>
|
<span class="stat-item">Requirements: {stats.requirementsPassed}/{stats.requirements}</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -302,12 +358,13 @@
|
|||||||
|
|
||||||
{#if auditReport}
|
{#if auditReport}
|
||||||
{@const status = getSpecificationStatus(index, auditReport.data)}
|
{@const status = getSpecificationStatus(index, auditReport.data)}
|
||||||
{#if ! status && spec.applicability["@maxOccurs"] == 0}
|
{#if status === false && usage === 'prohibited'}
|
||||||
{@const specReport = auditReport.data.specifications[index]}
|
{@const specReport = auditReport.data.specifications[index]}
|
||||||
|
{@const applicableEntities = specReport.applicable_entities ?? []}
|
||||||
<div class="entity-tables">
|
<div class="entity-tables">
|
||||||
{#if specReport.applicable_entities && specReport.applicable_entities.length > 0}
|
{#if applicableEntities.length > 0}
|
||||||
<div class="entity-table-section fail">
|
<div class="entity-table-section fail">
|
||||||
<h4>Failed Elements ({specReport.applicable_entities.length})</h4>
|
<h4>Failed Elements ({applicableEntities.length})</h4>
|
||||||
<div class="entity-table-container">
|
<div class="entity-table-container">
|
||||||
<Tooltip.Provider>
|
<Tooltip.Provider>
|
||||||
<table class="entity-table">
|
<table class="entity-table">
|
||||||
@@ -323,7 +380,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each specReport.applicable_entities.slice(0, 10) as entity}
|
{#each applicableEntities.slice(0, 10) as entity}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{entity.class}</td>
|
<td>{entity.class}</td>
|
||||||
<td>{entity.predefined_type || '-'}</td>
|
<td>{entity.predefined_type || '-'}</td>
|
||||||
@@ -379,9 +436,9 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
{#if specReport.applicable_entities.length > 10}
|
{#if applicableEntities.length > 10}
|
||||||
<tr class="more-row">
|
<tr class="more-row">
|
||||||
<td colspan="7">... {specReport.applicable_entities.length - 10} more failing elements not shown ...</td>
|
<td colspan="7">... {applicableEntities.length - 10} more failing elements not shown ...</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -397,37 +454,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Requirements Section -->
|
<!-- Requirements Section -->
|
||||||
{#if Array.isArray(spec.requirements) && spec.requirements.length > 0}
|
{#if requirementGroups.length > 0}
|
||||||
<div class="facet-section">
|
<div class="facet-section">
|
||||||
<h3>Requirements</h3>
|
<h3>Requirements</h3>
|
||||||
|
|
||||||
<div class="facets-list">
|
<div class="facets-list">
|
||||||
{#each Object.entries(spec.requirements || {}) as [facetType, facets]}
|
{#each requirementGroups as group}
|
||||||
{#if Array.isArray(facets) && facets.length > 0}
|
<div class="facet-group">
|
||||||
<div class="facet-group">
|
{#each group.items as item}
|
||||||
{#each facets as facet, facetIndex}
|
{@const reqAuditData = auditReport ? getRequirementStatus(index, item.reqIndex, auditReport.data) : null}
|
||||||
{@const reqAuditData = auditReport ? getRequirementStatus(index, facetIndex, auditReport.data) : null}
|
{@const specStatus = auditReport ? getSpecificationStatus(index, auditReport.data) : null}
|
||||||
{@const specStatus = auditReport ? getSpecificationStatus(index, auditReport.data) : null}
|
<div class="facet-item {auditReport && reqAuditData && specStatus !== 'skipped' ? (reqAuditData.status ? 'audit-pass' : 'audit-fail') : ''}">
|
||||||
<div class="facet-item {auditReport && reqAuditData && specStatus !== 'skipped' ? (reqAuditData.status ? 'audit-pass' : 'audit-fail') : ''}">
|
<button class="facet-header" onclick={() => {if (auditReport && reqAuditData && specStatus !== 'skipped') toggleRequirementDetails(index, item.reqIndex)}}>
|
||||||
<button class="facet-header" onclick={() => {if (auditReport && reqAuditData && specStatus !== 'skipped') toggleRequirementDetails(index, facetIndex)}}>
|
<span class="facet-bullet">•</span>
|
||||||
<span class="facet-bullet">•</span>
|
<span class="facet-text">{@html IDS.stringifyFacet("requirements", item.facet, group.facetType, spec)}</span>
|
||||||
<span class="facet-text">{@html IDS.stringifyFacet("requirements", facet, facetType, spec)}</span>
|
{#if auditReport && reqAuditData && specStatus !== 'skipped'}
|
||||||
{#if auditReport && reqAuditData && specStatus !== 'skipped'}
|
{#if reqAuditData.total_applicable > 0}
|
||||||
{#if reqAuditData.total_applicable > 0}
|
<div class="audit-details-toggle">
|
||||||
<div class="audit-details-toggle">
|
{reqAuditData.status ? 'PASS' : 'FAIL'} ({reqAuditData.total_pass}/{reqAuditData.total_applicable})
|
||||||
{reqAuditData.status ? 'PASS' : 'FAIL'} ({reqAuditData.total_pass}/{reqAuditData.total_applicable})
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:rotated={isRequirementDetailsExpanded(index, item.reqIndex)}>
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:rotated={isRequirementDetailsExpanded(index, facetIndex)}>
|
<polyline points="6,9 12,15 18,9"></polyline>
|
||||||
<polyline points="6,9 12,15 18,9"></polyline>
|
</svg>
|
||||||
</svg>
|
</div>
|
||||||
</div>
|
{:else}
|
||||||
{:else}
|
<span class="audit-status-badge">
|
||||||
<span class="audit-status-badge">
|
{reqAuditData.status ? 'PASS' : 'FAIL'}
|
||||||
{reqAuditData.status ? 'PASS' : 'FAIL'}
|
</span>
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
{/if}
|
||||||
{#if isRequirementDetailsExpanded(index, facetIndex)}
|
</button>
|
||||||
|
{#if reqAuditData && isRequirementDetailsExpanded(index, item.reqIndex)}
|
||||||
<div class="facet-expansion">
|
<div class="facet-expansion">
|
||||||
<div class="entity-tables">
|
<div class="entity-tables">
|
||||||
{#if reqAuditData.passed_entities && reqAuditData.passed_entities.length > 0}
|
{#if reqAuditData.passed_entities && reqAuditData.passed_entities.length > 0}
|
||||||
@@ -592,11 +648,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,37 +1,53 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
import * as IDS from "$src/modules/api/ids.svelte";
|
||||||
import FacetEditor from './FacetEditor.svelte';
|
import FacetEditor from './FacetEditor.svelte';
|
||||||
import CreateFacetDropdown from "$src/components/CreateFacetDropdown.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 activeDocument = $derived(
|
||||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
);
|
||||||
activeDocument.specifications.specification[documentState.activeSpecification] : 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) {
|
async function addFacet(facetType: FacetType) {
|
||||||
if (!activeSpecification) return;
|
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||||
|
|
||||||
await IDS.createFacet(
|
await IDS.createFacet(
|
||||||
IDS.Module.activeDocument,
|
IDS.Module.activeDocument,
|
||||||
documentState.activeSpecification,
|
documentState.activeSpecification ?? 0,
|
||||||
"requirements",
|
"requirements",
|
||||||
facetType
|
facetType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeFacet(facetType, facetIndex) {
|
async function removeFacet(facetType: FacetType, facetIndex: number) {
|
||||||
if (!activeSpecification) return;
|
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||||
|
|
||||||
await IDS.deleteFacet(
|
await IDS.deleteFacet(
|
||||||
IDS.Module.activeDocument,
|
IDS.Module.activeDocument,
|
||||||
documentState.activeSpecification,
|
documentState.activeSpecification ?? 0,
|
||||||
"requirements",
|
"requirements",
|
||||||
facetType,
|
facetType,
|
||||||
facetIndex
|
facetIndex
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let requirementEntries = $derived(
|
||||||
|
activeSpecification?.requirements
|
||||||
|
? (Object.entries(activeSpecification.requirements).filter(([, facets]) =>
|
||||||
|
Array.isArray(facets)
|
||||||
|
) as Array<[FacetType, Facet[]]>)
|
||||||
|
: []
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="restrictions-panel">
|
<div class="restrictions-panel">
|
||||||
@@ -40,8 +56,8 @@
|
|||||||
<CreateFacetDropdown {addFacet} />
|
<CreateFacetDropdown {addFacet} />
|
||||||
</div>
|
</div>
|
||||||
<div class="restrictions-list">
|
<div class="restrictions-list">
|
||||||
{#if activeSpecification?.requirements}
|
{#if activeSpecification && requirementEntries.length > 0}
|
||||||
{#each Object.entries(activeSpecification.requirements) as [facetType, facets]}
|
{#each requirementEntries as [facetType, facets]}
|
||||||
{#each facets as facet, index}
|
{#each facets as facet, index}
|
||||||
<FacetEditor
|
<FacetEditor
|
||||||
bind:facet={facets[index]}
|
bind:facet={facets[index]}
|
||||||
@@ -50,10 +66,9 @@
|
|||||||
activeTab="requirements"
|
activeTab="requirements"
|
||||||
{removeFacet}
|
{removeFacet}
|
||||||
{index}
|
{index}
|
||||||
key={`${activeDocument}-${documentState?.activeSpecification}-requirements-${facetType}-${index}`}
|
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,34 +1,86 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import Svelecte from 'svelecte';
|
import Svelecte from 'svelecte';
|
||||||
import { getEntityClasses, getMaterialCategories, getClassificationSystems, getDataTypes, getPredefinedTypes, getEntityAttributes, getApplicablePsets } from '$src/modules/api/api.svelte.js';
|
import {
|
||||||
import * as IDS from '$src/modules/api/ids.svelte.js';
|
getApplicablePsets,
|
||||||
|
getClassificationSystems,
|
||||||
|
getDataTypes,
|
||||||
|
getEntityAttributes,
|
||||||
|
getEntityClasses,
|
||||||
|
getMaterialCategories,
|
||||||
|
getPredefinedTypes
|
||||||
|
} 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'
|
||||||
const isEntityNameField = autocomplete === 'entityName';
|
| 'material'
|
||||||
const isMaterialField = autocomplete === 'material';
|
| 'classificationSystem'
|
||||||
const isClassificationSystemField = autocomplete === 'classificationSystem';
|
| 'predefinedType'
|
||||||
const isPredefinedTypeField = autocomplete === 'predefinedType';
|
| 'attributeName'
|
||||||
const isAttributeNameField = autocomplete === 'attributeName';
|
| 'propertySet'
|
||||||
const isPropertySetField = autocomplete === 'propertySet';
|
| 'dataType'
|
||||||
const isDataTypeField = autocomplete === '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();
|
||||||
|
|
||||||
|
let isEntityNameField = $derived(autocomplete === 'entityName');
|
||||||
|
let isMaterialField = $derived(autocomplete === 'material');
|
||||||
|
let isClassificationSystemField = $derived(autocomplete === 'classificationSystem');
|
||||||
|
let isPredefinedTypeField = $derived(autocomplete === 'predefinedType');
|
||||||
|
let isAttributeNameField = $derived(autocomplete === 'attributeName');
|
||||||
|
let isPropertySetField = $derived(autocomplete === 'propertySet');
|
||||||
|
let isDataTypeField = $derived(autocomplete === 'dataType');
|
||||||
|
|
||||||
|
const uniqueId = Math.random().toString(36).slice(2, 8);
|
||||||
|
let baseId = $derived(`restriction-${fieldName}-${uniqueId}`);
|
||||||
|
|
||||||
|
const activeDocument = $derived(
|
||||||
|
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||||
|
);
|
||||||
|
const documentState = $derived(
|
||||||
|
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||||
|
);
|
||||||
|
const activeSpecification = $derived(
|
||||||
|
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||||
|
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
const getFieldValue = () => (facet as Record<string, unknown>)[fieldName] as FacetValue | string | undefined;
|
||||||
|
const setFieldValue = (value: FacetValue | string) => {
|
||||||
|
(facet as Record<string, unknown>)[fieldName] = value;
|
||||||
|
};
|
||||||
|
const getFacetValue = () => {
|
||||||
|
const value = getFieldValue();
|
||||||
|
return typeof value === 'object' && value !== null ? (value as FacetValue) : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
// Predefined Types autocompletions
|
// Predefined Types autocompletions
|
||||||
let predefinedTypeOptions = $state([]);
|
let predefinedTypeOptions: string[] = $state([]);
|
||||||
|
|
||||||
// Attribute Names autocompletions
|
// Attribute Names autocompletions
|
||||||
let attributeNameOptions = $state([]);
|
let attributeNameOptions: string[] = $state([]);
|
||||||
|
|
||||||
// Property Sets autocompletions
|
// Property Sets autocompletions
|
||||||
let propertySetOptions = $state([]);
|
let propertySetOptions: string[] = $state([]);
|
||||||
|
|
||||||
// Get the active specification's IFC schemas
|
// Get the active specification's IFC schemas
|
||||||
const getIfcVersions = () => {
|
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 versions = activeSpecification?.["@ifcVersion"] || ['IFC4'];
|
const versions = activeSpecification?.["@ifcVersion"] || ['IFC4'];
|
||||||
// TODO: Fix: Filter out IFC4X3 because it's buggy
|
// TODO: Fix: Filter out IFC4X3 because it's buggy
|
||||||
return versions.filter(version => version !== 'IFC4X3_ADD2');
|
return versions.filter(version => version !== 'IFC4X3_ADD2');
|
||||||
@@ -36,7 +88,7 @@
|
|||||||
|
|
||||||
// Get the entity name from facet
|
// Get the entity name from facet
|
||||||
const getEntityName = () => {
|
const getEntityName = () => {
|
||||||
const nameField = facet?.name;
|
const nameField = (facet as Record<string, unknown>)?.name as FacetValue | undefined;
|
||||||
if (!nameField) return '';
|
if (!nameField) return '';
|
||||||
if (nameField.simpleValue) return nameField.simpleValue;
|
if (nameField.simpleValue) return nameField.simpleValue;
|
||||||
return '';
|
return '';
|
||||||
@@ -44,18 +96,13 @@
|
|||||||
|
|
||||||
// Get all entity names from Entity facets in Applicability
|
// Get all entity names from Entity facets in Applicability
|
||||||
const getApplicabilityEntityNames = () => {
|
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;
|
|
||||||
|
|
||||||
if (!activeSpecification?.applicability?.entity) return [];
|
if (!activeSpecification?.applicability?.entity) return [];
|
||||||
|
|
||||||
const entityFacets = activeSpecification.applicability.entity;
|
const entityFacets = activeSpecification.applicability.entity as Facet[];
|
||||||
const entityNames = [];
|
const entityNames: string[] = [];
|
||||||
|
|
||||||
entityFacets.forEach(entityFacet => {
|
entityFacets.forEach(entityFacet => {
|
||||||
const nameField = entityFacet.name;
|
const nameField = (entityFacet as Record<string, unknown>).name as FacetValue | undefined;
|
||||||
if (!nameField) return;
|
if (!nameField) return;
|
||||||
|
|
||||||
if (nameField.simpleValue) {
|
if (nameField.simpleValue) {
|
||||||
@@ -74,19 +121,14 @@
|
|||||||
|
|
||||||
// Get entity facets with their predefined types
|
// Get entity facets with their predefined types
|
||||||
const getApplicabilityEntityFacets = () => {
|
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;
|
|
||||||
|
|
||||||
if (!activeSpecification?.applicability?.entity) return [];
|
if (!activeSpecification?.applicability?.entity) return [];
|
||||||
|
|
||||||
return activeSpecification.applicability.entity.map(entityFacet => {
|
return (activeSpecification.applicability.entity as Facet[]).map(entityFacet => {
|
||||||
const nameField = entityFacet.name;
|
const nameField = (entityFacet as Record<string, unknown>).name as FacetValue | undefined;
|
||||||
const predefinedTypeField = entityFacet.predefinedType;
|
const predefinedTypeField = (entityFacet as Record<string, unknown>).predefinedType as FacetValue | undefined;
|
||||||
|
|
||||||
// Extract entity name
|
// Extract entity name
|
||||||
let entityNames = [];
|
let entityNames: string[] = [];
|
||||||
if (nameField?.simpleValue) {
|
if (nameField?.simpleValue) {
|
||||||
entityNames = [nameField.simpleValue];
|
entityNames = [nameField.simpleValue];
|
||||||
} else if (nameField?.restriction?.enumeration) {
|
} else if (nameField?.restriction?.enumeration) {
|
||||||
@@ -99,9 +141,12 @@
|
|||||||
let predefinedType = '';
|
let predefinedType = '';
|
||||||
if (predefinedTypeField?.simpleValue) {
|
if (predefinedTypeField?.simpleValue) {
|
||||||
predefinedType = predefinedTypeField.simpleValue;
|
predefinedType = predefinedTypeField.simpleValue;
|
||||||
} else if (predefinedTypeField?.restriction?.enumeration?.length > 0) {
|
} else {
|
||||||
// For enumeration predefined types, use the first one or empty string
|
const enumValues = predefinedTypeField?.restriction?.enumeration;
|
||||||
predefinedType = predefinedTypeField.restriction.enumeration[0]['@value'] || '';
|
if (enumValues && enumValues.length > 0) {
|
||||||
|
// For enumeration predefined types, use the first one or empty string
|
||||||
|
predefinedType = enumValues[0]['@value'] || '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { entityNames, predefinedType };
|
return { entityNames, predefinedType };
|
||||||
@@ -109,7 +154,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Contextual autocompletions
|
// Contextual autocompletions
|
||||||
$effect(async () => {
|
$effect(() => {
|
||||||
|
void (async () => {
|
||||||
|
|
||||||
// Predefined Types
|
// Predefined Types
|
||||||
if (isPredefinedTypeField) {
|
if (isPredefinedTypeField) {
|
||||||
@@ -122,10 +168,10 @@
|
|||||||
const typePromises = ifcVersions.map(schema =>
|
const typePromises = ifcVersions.map(schema =>
|
||||||
getPredefinedTypes(schema, entityName.toUpperCase())
|
getPredefinedTypes(schema, entityName.toUpperCase())
|
||||||
);
|
);
|
||||||
const typeSets = await Promise.all(typePromises);
|
const typeSets = await Promise.all(typePromises) as string[][];
|
||||||
|
|
||||||
// Deduplicate predefined types across all schemas
|
// Deduplicate predefined types across all schemas
|
||||||
const allTypes = new Set();
|
const allTypes = new Set<string>();
|
||||||
typeSets.forEach(types => {
|
typeSets.forEach(types => {
|
||||||
if (types && Array.isArray(types)) {
|
if (types && Array.isArray(types)) {
|
||||||
types.forEach(type => allTypes.add(type));
|
types.forEach(type => allTypes.add(type));
|
||||||
@@ -150,19 +196,19 @@
|
|||||||
if (entityNames.length > 0 && ifcVersions.length > 0) {
|
if (entityNames.length > 0 && ifcVersions.length > 0) {
|
||||||
try {
|
try {
|
||||||
// Fetch attributes for all entity names across all selected schemas
|
// Fetch attributes for all entity names across all selected schemas
|
||||||
const attributePromises = [];
|
const attributePromises: Array<Promise<{ name: string }[]>> = [];
|
||||||
entityNames.forEach(entityName => {
|
entityNames.forEach(entityName => {
|
||||||
ifcVersions.forEach(schema => {
|
ifcVersions.forEach(schema => {
|
||||||
attributePromises.push(
|
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
|
// Deduplicate attribute names across all entities and schemas
|
||||||
const allAttributes = new Set();
|
const allAttributes = new Set<string>();
|
||||||
attributeSets.forEach(attributes => {
|
attributeSets.forEach(attributes => {
|
||||||
if (attributes && Array.isArray(attributes)) {
|
if (attributes && Array.isArray(attributes)) {
|
||||||
attributes.forEach(attr => {
|
attributes.forEach(attr => {
|
||||||
@@ -191,21 +237,21 @@
|
|||||||
if (entityFacets.length > 0 && ifcVersions.length > 0) {
|
if (entityFacets.length > 0 && ifcVersions.length > 0) {
|
||||||
try {
|
try {
|
||||||
// Fetch applicable property sets for all entity facets across all selected schemas
|
// Fetch applicable property sets for all entity facets across all selected schemas
|
||||||
const psetPromises = [];
|
const psetPromises: Array<Promise<string[]>> = [];
|
||||||
entityFacets.forEach(facet => {
|
entityFacets.forEach(facet => {
|
||||||
facet.entityNames.forEach(entityName => {
|
facet.entityNames.forEach(entityName => {
|
||||||
ifcVersions.forEach(schema => {
|
ifcVersions.forEach(schema => {
|
||||||
psetPromises.push(
|
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
|
// Deduplicate property set names
|
||||||
const allPsets = new Set();
|
const allPsets = new Set<string>();
|
||||||
psetSets.forEach(psets => {
|
psetSets.forEach(psets => {
|
||||||
if (psets && Array.isArray(psets)) {
|
if (psets && Array.isArray(psets)) {
|
||||||
psets.forEach(pset => allPsets.add(pset));
|
psets.forEach(pset => allPsets.add(pset));
|
||||||
@@ -221,10 +267,11 @@
|
|||||||
propertySetOptions = [];
|
propertySetOptions = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get autocomplete options based on field type
|
// Get autocomplete options based on field type
|
||||||
const getAutocompleteOptions = () => {
|
const getAutocompleteOptions = (): string[] => {
|
||||||
if (isEntityNameField) return getEntityClasses();
|
if (isEntityNameField) return getEntityClasses();
|
||||||
if (isMaterialField) return getMaterialCategories();
|
if (isMaterialField) return getMaterialCategories();
|
||||||
if (isClassificationSystemField) return Object.keys(getClassificationSystems());
|
if (isClassificationSystemField) return Object.keys(getClassificationSystems());
|
||||||
@@ -236,11 +283,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getRestrictionType = () => {
|
const getRestrictionType = () => {
|
||||||
const fieldValue = facet[fieldName];
|
const fieldValue = getFacetValue();
|
||||||
if (!fieldValue) return 'Simple';
|
if (!fieldValue) return 'Simple';
|
||||||
if (fieldValue.simpleValue !== undefined) return 'Simple';
|
if (fieldValue.simpleValue !== undefined) return 'Simple';
|
||||||
if (fieldValue['restriction']) {
|
if (fieldValue['restriction']) {
|
||||||
const restriction = fieldValue['restriction'];
|
const restriction = fieldValue['restriction'] as Restriction;
|
||||||
if (restriction['enumeration']) return 'Enumeration';
|
if (restriction['enumeration']) return 'Enumeration';
|
||||||
if (restriction['pattern']) return 'Pattern';
|
if (restriction['pattern']) return 'Pattern';
|
||||||
if (restriction['minInclusive'] || restriction['maxInclusive'] ||
|
if (restriction['minInclusive'] || restriction['maxInclusive'] ||
|
||||||
@@ -251,39 +298,40 @@
|
|||||||
return 'Simple';
|
return 'Simple';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSimpleValue = () => {
|
const getSimpleValue = (): string => {
|
||||||
const fieldValue = facet[fieldName];
|
const fieldValue = getFieldValue();
|
||||||
|
|
||||||
// For special properties (eg. @dataType), we return the value directly
|
// 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;
|
if (fieldValue.simpleValue !== undefined) return fieldValue.simpleValue;
|
||||||
return null;
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEnumerationValues = () => {
|
const getEnumerationValues = () => {
|
||||||
const fieldValue = facet[fieldName];
|
const fieldValue = getFacetValue();
|
||||||
if (!fieldValue?.['restriction']) return [''];
|
if (!fieldValue?.['restriction']) return [''];
|
||||||
const restriction = fieldValue['restriction'];
|
const restriction = fieldValue['restriction'] as Restriction;
|
||||||
const enumValues = restriction['enumeration'];
|
const enumValues = restriction['enumeration'] as RestrictionValue[] | undefined;
|
||||||
if (!enumValues) return [''];
|
if (!enumValues) return [''];
|
||||||
return enumValues.map(item => item['@value'] || '');
|
return enumValues.map(item => item['@value'] || '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPatternValue = () => {
|
const getPatternValue = () => {
|
||||||
const fieldValue = facet[fieldName];
|
const fieldValue = getFacetValue();
|
||||||
if (!fieldValue?.['restriction']) return '';
|
if (!fieldValue?.['restriction']) return '';
|
||||||
const restriction = fieldValue['restriction'];
|
const restriction = fieldValue['restriction'] as Restriction;
|
||||||
const pattern = restriction['pattern'];
|
const pattern = restriction['pattern'] as RestrictionValue[] | undefined;
|
||||||
if (!pattern || !pattern.length) return '';
|
if (!pattern || !pattern.length) return '';
|
||||||
return pattern[0]['@value'] || '';
|
return pattern[0]['@value'] || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRangeValues = () => {
|
const getRangeValues = () => {
|
||||||
const fieldValue = facet[fieldName];
|
const fieldValue = getFacetValue();
|
||||||
if (!fieldValue?.['restriction']) return { min: '', max: '', minType: 'Inclusive', maxType: 'Inclusive' };
|
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';
|
let min = '', max = '', minType = 'Inclusive', maxType = 'Inclusive';
|
||||||
|
|
||||||
@@ -307,18 +355,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getLengthValue = () => {
|
const getLengthValue = () => {
|
||||||
const fieldValue = facet[fieldName];
|
const fieldValue = getFacetValue();
|
||||||
if (!fieldValue?.['restriction']) return '';
|
if (!fieldValue?.['restriction']) return '';
|
||||||
const restriction = fieldValue['restriction'];
|
const restriction = fieldValue['restriction'] as Restriction;
|
||||||
const length = restriction['length'];
|
const length = restriction['length'] as RestrictionValue[] | undefined;
|
||||||
if (!length || !length.length) return '';
|
if (!length || !length.length) return '';
|
||||||
return length[0]['@value'] || '';
|
return length[0]['@value'] || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLengthRangeValues = () => {
|
const getLengthRangeValues = () => {
|
||||||
const fieldValue = facet[fieldName];
|
const fieldValue = getFacetValue();
|
||||||
if (!fieldValue?.['restriction']) return { min: '', max: '' };
|
if (!fieldValue?.['restriction']) return { min: '', max: '' };
|
||||||
const restriction = fieldValue['restriction'];
|
const restriction = fieldValue['restriction'] as Restriction;
|
||||||
|
|
||||||
let min = '', max = '';
|
let min = '', max = '';
|
||||||
|
|
||||||
@@ -332,77 +380,85 @@
|
|||||||
return { min, max };
|
return { min, max };
|
||||||
};
|
};
|
||||||
|
|
||||||
const setSimpleValue = (value) => {
|
const setSimpleValue = (value: string) => {
|
||||||
// For special properties (eg. @dataType), we set the value directly
|
// For special properties (eg. @dataType), we set the value directly
|
||||||
if (isSpecialProp) {
|
if (isSpecialProp) {
|
||||||
facet[fieldName] = value;
|
setFieldValue(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!facet[fieldName]) facet[fieldName] = {};
|
setFieldValue({ simpleValue: value });
|
||||||
facet[fieldName] = { simpleValue: value };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const setEnumerationValues = (values) => {
|
const setEnumerationValues = (values: string[]) => {
|
||||||
if (!facet[fieldName]) facet[fieldName] = {};
|
|
||||||
const enumItems = values.filter(v => v && typeof v === 'string' && v.trim() !== '').map(v => ({ '@value': v }));
|
const enumItems = values.filter(v => v && typeof v === 'string' && v.trim() !== '').map(v => ({ '@value': v }));
|
||||||
facet[fieldName] = {
|
setFieldValue({
|
||||||
'restriction': {
|
'restriction': {
|
||||||
'@base': 'xs:string',
|
'@base': 'xs:string',
|
||||||
'enumeration': enumItems
|
'enumeration': enumItems
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPatternValue = (value) => {
|
const setPatternValue = (value: string) => {
|
||||||
if (!facet[fieldName]) facet[fieldName] = {};
|
setFieldValue({
|
||||||
facet[fieldName] = {
|
|
||||||
'restriction': {
|
'restriction': {
|
||||||
'@base': 'xs:string',
|
'@base': 'xs:string',
|
||||||
'pattern': [{ '@value': value }]
|
'pattern': [{ '@value': value }]
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setRangeValues = (min, max, minType, maxType) => {
|
const setRangeValues = (min: string, max: string, minType: string, maxType: string) => {
|
||||||
if (!facet[fieldName]) facet[fieldName] = {};
|
const restriction: Restriction = { '@base': 'xs:string' };
|
||||||
const restriction = { '@base': 'xs:string' };
|
|
||||||
|
|
||||||
if (min !== '') {
|
if (min !== '') {
|
||||||
const minKey = minType === 'Inclusive' ? 'minInclusive' : 'minExclusive';
|
const minKey = minType === 'Inclusive' ? 'minInclusive' : 'minExclusive';
|
||||||
restriction[minKey] = [{ '@value': min }];
|
(restriction as Record<string, RestrictionValue[]>)[minKey] = [{ '@value': min }];
|
||||||
}
|
}
|
||||||
if (max !== '') {
|
if (max !== '') {
|
||||||
const maxKey = maxType === 'Inclusive' ? 'maxInclusive' : 'maxExclusive';
|
const maxKey = maxType === 'Inclusive' ? 'maxInclusive' : 'maxExclusive';
|
||||||
restriction[maxKey] = [{ '@value': max }];
|
(restriction as Record<string, RestrictionValue[]>)[maxKey] = [{ '@value': max }];
|
||||||
}
|
}
|
||||||
|
|
||||||
facet[fieldName] = { 'restriction': restriction };
|
setFieldValue({ 'restriction': restriction });
|
||||||
};
|
};
|
||||||
|
|
||||||
const setLengthValue = (value) => {
|
const setLengthValue = (value: string) => {
|
||||||
if (!facet[fieldName]) facet[fieldName] = {};
|
setFieldValue({
|
||||||
facet[fieldName] = {
|
|
||||||
'restriction': {
|
'restriction': {
|
||||||
'@base': 'xs:string',
|
'@base': 'xs:string',
|
||||||
'length': [{ '@value': value }]
|
'length': [{ '@value': value }]
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setLengthRangeValues = (min, max) => {
|
const setLengthRangeValues = (min: string, max: string) => {
|
||||||
if (!facet[fieldName]) facet[fieldName] = {};
|
const restriction: Restriction = { '@base': 'xs:string' };
|
||||||
const restriction = { '@base': 'xs:string' };
|
|
||||||
|
|
||||||
if (min !== '') restriction['minLength'] = [{ '@value': min }];
|
if (min !== '') restriction['minLength'] = [{ '@value': min }];
|
||||||
if (max !== '') restriction['maxLength'] = [{ '@value': max }];
|
if (max !== '') restriction['maxLength'] = [{ '@value': max }];
|
||||||
|
|
||||||
facet[fieldName] = { 'restriction': restriction };
|
setFieldValue({ 'restriction': restriction });
|
||||||
};
|
};
|
||||||
|
|
||||||
let restrictionType = $derived(getRestrictionType());
|
let restrictionType = $state(getRestrictionType());
|
||||||
|
let hasUserSelectedType = $state(false);
|
||||||
|
let lastFacetRef = $state(facet);
|
||||||
let enumerationValues = $derived(getEnumerationValues());
|
let enumerationValues = $derived(getEnumerationValues());
|
||||||
|
|
||||||
const handleTypeChange = (newType) => {
|
$effect(() => {
|
||||||
|
if (facet !== lastFacetRef) {
|
||||||
|
lastFacetRef = facet;
|
||||||
|
hasUserSelectedType = false;
|
||||||
|
}
|
||||||
|
const detected = getRestrictionType();
|
||||||
|
if (!hasUserSelectedType || detected !== 'Simple' || restrictionType === 'Simple') {
|
||||||
|
restrictionType = detected;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTypeChange = (newType: string) => {
|
||||||
|
hasUserSelectedType = true;
|
||||||
restrictionType = newType;
|
restrictionType = newType;
|
||||||
|
|
||||||
switch (newType) {
|
switch (newType) {
|
||||||
@@ -435,23 +491,27 @@
|
|||||||
enumerationValues = [...enumerationValues, ''];
|
enumerationValues = [...enumerationValues, ''];
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeEnumerationValue = (index) => {
|
const removeEnumerationValue = (index: number) => {
|
||||||
enumerationValues = enumerationValues.filter((_, i) => i !== index);
|
enumerationValues = enumerationValues.filter((_, i) => i !== index);
|
||||||
setEnumerationValues(enumerationValues);
|
setEnumerationValues(enumerationValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateEnumerationValue = (index, value) => {
|
const updateEnumerationValue = (index: number, value: string) => {
|
||||||
enumerationValues[index] = value;
|
enumerationValues[index] = value;
|
||||||
setEnumerationValues(enumerationValues);
|
setEnumerationValues(enumerationValues);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{label}</label>
|
<span class="form-label" id={`${baseId}-label`}>{label}</span>
|
||||||
<div class="restriction-controls">
|
<div class="restriction-controls" role="group" aria-labelledby={`${baseId}-label`}>
|
||||||
{#if !isSpecialProp}
|
{#if !isSpecialProp}
|
||||||
<div class="restriction-type-selector">
|
<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="Simple">Simple</option>
|
||||||
<option value="Enumeration">Enumeration</option>
|
<option value="Enumeration">Enumeration</option>
|
||||||
<option value="Pattern">Pattern</option>
|
<option value="Pattern">Pattern</option>
|
||||||
@@ -477,7 +537,7 @@
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
|
|
||||||
{:else if restrictionType === 'Enumeration'}
|
{:else if restrictionType === 'Enumeration'}
|
||||||
@@ -497,7 +557,14 @@
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
<button class="btn-delete" onclick={() => removeEnumerationValue(index)} type="button" aria-label="Remove enumeration value">
|
<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">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
@@ -515,22 +582,43 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{:else if restrictionType === 'Pattern'}
|
{: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'}
|
{:else if restrictionType === 'Range'}
|
||||||
|
{@const range = getRangeValues()}
|
||||||
<div class="range-controls">
|
<div class="range-controls">
|
||||||
<div class="range-group">
|
<div class="range-group">
|
||||||
<label>Min</label>
|
<label for={`${baseId}-range-min`}>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">
|
<input
|
||||||
<select class="form-input" bind:value={() => getRangeValues().minType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, v, range.maxType); }}>
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
id={`${baseId}-range-min`}
|
||||||
|
bind:value={() => range.min, (v) => setRangeValues(v, range.max, range.minType, range.maxType)}
|
||||||
|
placeholder="0"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
class="form-input"
|
||||||
|
aria-label="Min bound type"
|
||||||
|
bind:value={() => range.minType, (v) => setRangeValues(range.min, range.max, v, range.maxType)}
|
||||||
|
>
|
||||||
<option value="Inclusive">Inclusive</option>
|
<option value="Inclusive">Inclusive</option>
|
||||||
<option value="Exclusive">Exclusive</option>
|
<option value="Exclusive">Exclusive</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="range-group">
|
<div class="range-group">
|
||||||
<label>Max</label>
|
<label for={`${baseId}-range-max`}>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">
|
<input
|
||||||
<select class="form-input" bind:value={() => getRangeValues().maxType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, range.minType, v); }}>
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
id={`${baseId}-range-max`}
|
||||||
|
bind:value={() => range.max, (v) => setRangeValues(range.min, v, range.minType, range.maxType)}
|
||||||
|
placeholder="0"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
class="form-input"
|
||||||
|
aria-label="Max bound type"
|
||||||
|
bind:value={() => range.maxType, (v) => setRangeValues(range.min, range.max, range.minType, v)}
|
||||||
|
>
|
||||||
<option value="Inclusive">Inclusive</option>
|
<option value="Inclusive">Inclusive</option>
|
||||||
<option value="Exclusive">Exclusive</option>
|
<option value="Exclusive">Exclusive</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -538,17 +626,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{:else if restrictionType === 'Length'}
|
{: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'}
|
{:else if restrictionType === 'Length Range'}
|
||||||
|
{@const lengthRange = getLengthRangeValues()}
|
||||||
<div class="length-range-controls">
|
<div class="length-range-controls">
|
||||||
<div class="length-group">
|
<div class="length-group">
|
||||||
<label>Min Length</label>
|
<label for={`${baseId}-length-min`}>Min Length</label>
|
||||||
<input class="form-input" type="number" bind:value={() => getLengthRangeValues().min, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(v, range.max); }} placeholder="0">
|
<input class="form-input" id={`${baseId}-length-min`} type="number" bind:value={() => lengthRange.min, (v) => setLengthRangeValues(v, lengthRange.max)} placeholder="0">
|
||||||
</div>
|
</div>
|
||||||
<div class="length-group">
|
<div class="length-group">
|
||||||
<label>Max Length</label>
|
<label for={`${baseId}-length-max`}>Max Length</label>
|
||||||
<input class="form-input" type="number" bind:value={() => getLengthRangeValues().max, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(range.min, v); }} placeholder="0">
|
<input class="form-input" id={`${baseId}-length-max`} type="number" bind:value={() => lengthRange.max, (v) => setLengthRangeValues(lengthRange.min, v)} placeholder="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -656,4 +745,4 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,33 +1,44 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
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 activeDocument = $derived(
|
||||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
);
|
||||||
activeDocument.specifications.specification[documentState.activeSpecification] : 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) => {
|
const getProp = (prop: string) => {
|
||||||
return activeSpecification?.[prop] ?? "";
|
const value = (activeSpecification as Record<string, unknown>)?.[prop];
|
||||||
|
return typeof value === "string" ? value : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const setProp = (prop, value) => {
|
const setProp = (prop: string, value: string) => {
|
||||||
activeSpecification[prop] = value;
|
if (!activeSpecification) return;
|
||||||
|
(activeSpecification as Record<string, unknown>)[prop] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addIfcVersion = (e, version) => {
|
const addIfcVersion = (e: Event, version: string) => {
|
||||||
if (activeSpecification) {
|
if (activeSpecification) {
|
||||||
if (!("@ifcVersion" in activeSpecification)) activeSpecification["@ifcVersion"] = [];
|
if (!("@ifcVersion" in activeSpecification)) activeSpecification["@ifcVersion"] = [];
|
||||||
if (e.target.checked) {
|
const target = e.target as HTMLInputElement | null;
|
||||||
if (!activeSpecification["@ifcVersion"].includes(version)) {
|
if (target?.checked) {
|
||||||
activeSpecification["@ifcVersion"] = [...activeSpecification["@ifcVersion"], version];
|
if (!activeSpecification["@ifcVersion"]?.includes(version)) {
|
||||||
|
activeSpecification["@ifcVersion"] = [...(activeSpecification["@ifcVersion"] ?? []), version];
|
||||||
}
|
}
|
||||||
} else {
|
} else if (activeSpecification["@ifcVersion"]) {
|
||||||
activeSpecification["@ifcVersion"] = activeSpecification["@ifcVersion"].filter(v => v !== version);
|
activeSpecification["@ifcVersion"] = activeSpecification["@ifcVersion"].filter(v => v !== version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setUsage = (usage) => {
|
const setUsage = (usage: IdsCardinality) => {
|
||||||
if (!activeSpecification) return;
|
if (!activeSpecification) return;
|
||||||
if (!activeSpecification.applicability) activeSpecification.applicability = {};
|
if (!activeSpecification.applicability) activeSpecification.applicability = {};
|
||||||
|
|
||||||
@@ -60,14 +71,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="spec-cardinality">Usage</label>
|
<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="required">Required</option>
|
||||||
<option value="optional">Optional</option>
|
<option value="optional">Optional</option>
|
||||||
<option value="prohibited">Prohibited</option>
|
<option value="prohibited">Prohibited</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group full-width">
|
<div class="form-group full-width">
|
||||||
<label>IFC Version</label>
|
<span class="form-label">IFC Version</span>
|
||||||
<div class="radio-group">
|
<div class="radio-group">
|
||||||
<label class="radio-label">
|
<label class="radio-label">
|
||||||
<input type="checkbox" value="IFC2X3" checked={activeSpecification?.["@ifcVersion"]?.includes('IFC2X3')} onchange={(e) => addIfcVersion(e, 'IFC2X3')}>
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
import * as IDS from "$src/modules/api/ids.svelte";
|
||||||
import AppHeader from "$src/components/AppHeader.svelte";
|
import AppHeader from "$src/components/AppHeader.svelte";
|
||||||
import AppRibbon from "$src/components/AppRibbon.svelte";
|
import AppRibbon from "$src/components/AppRibbon.svelte";
|
||||||
import AppToolbar from "$src/components/AppToolbar.svelte";
|
import AppToolbar from "$src/components/AppToolbar.svelte";
|
||||||
@@ -11,14 +11,26 @@
|
|||||||
import IdsViewer from "./IdsViewer.svelte";
|
import IdsViewer from "./IdsViewer.svelte";
|
||||||
import SplashScreen from "$src/components/SplashScreen.svelte";
|
import SplashScreen from "$src/components/SplashScreen.svelte";
|
||||||
import { Toaster } from "$lib/components/ui/sonner";
|
import { Toaster } from "$lib/components/ui/sonner";
|
||||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||||
import {onMount} from "svelte";
|
import type { DocumentState, IdsDocument, Specification } from "$src/types/ids";
|
||||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||||
|
|
||||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
let activeDocument = $derived(
|
||||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
);
|
||||||
activeDocument.specifications.specification[documentState.activeSpecification] : 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
|
||||||
|
);
|
||||||
|
let importableDocuments = $derived(
|
||||||
|
Object.entries(IDS.Module.documents).filter(
|
||||||
|
([docId, doc]) => docId !== IDS.Module.activeDocument && doc.specifications?.specification?.length > 0
|
||||||
|
) as [string, IdsDocument][]
|
||||||
|
);
|
||||||
|
|
||||||
async function addNewSpecification() {
|
async function addNewSpecification() {
|
||||||
if (!IDS.Module.activeDocument) return;
|
if (!IDS.Module.activeDocument) return;
|
||||||
@@ -28,11 +40,11 @@
|
|||||||
IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'info' });
|
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;
|
if (!IDS.Module.activeDocument || !IDS.Module.documents[sourceDocId]) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sourceDoc = IDS.Module.documents[sourceDocId];
|
const sourceDoc = IDS.Module.documents[sourceDocId] as IdsDocument;
|
||||||
const sourceSpec = sourceDoc.specifications?.specification?.[specIndex];
|
const sourceSpec = sourceDoc.specifications?.specification?.[specIndex];
|
||||||
|
|
||||||
if (!sourceSpec) {
|
if (!sourceSpec) {
|
||||||
@@ -41,13 +53,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a deep copy of the specification
|
// 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
|
// Add the copied specification to the current document
|
||||||
IDS.Module.documents[IDS.Module.activeDocument].specifications.specification.push(specCopy);
|
IDS.Module.documents[IDS.Module.activeDocument].specifications.specification.push(specCopy);
|
||||||
|
|
||||||
// Switch to the newly created specification and info tab
|
// 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;
|
const newSpecIndex = (currentDoc.specifications?.specification?.length || 1) - 1;
|
||||||
IDS.setDocumentState(IDS.Module.activeDocument, {
|
IDS.setDocumentState(IDS.Module.activeDocument, {
|
||||||
activeSpecification: newSpecIndex,
|
activeSpecification: newSpecIndex,
|
||||||
@@ -57,17 +69,18 @@
|
|||||||
success(`Specification "${sourceSpec['@name'] || 'Unnamed'}" imported successfully`);
|
success(`Specification "${sourceSpec['@name'] || 'Unnamed'}" imported successfully`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error importing specification:', 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) {
|
if (IDS.Module.activeDocument) {
|
||||||
IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: index });
|
IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: index });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSpecification(specIndex) {
|
async function deleteSpecification(specIndex: number) {
|
||||||
if (!IDS.Module.activeDocument) return;
|
if (!IDS.Module.activeDocument) return;
|
||||||
await IDS.deleteSpecification(IDS.Module.activeDocument, specIndex);
|
await IDS.deleteSpecification(IDS.Module.activeDocument, specIndex);
|
||||||
}
|
}
|
||||||
@@ -83,9 +96,24 @@
|
|||||||
success('IDS document exported successfully');
|
success('IDS document exported successfully');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error exporting IDS:', 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>
|
</script>
|
||||||
|
|
||||||
{#if IDS.Module.status != "ready"}
|
{#if IDS.Module.status != "ready"}
|
||||||
@@ -130,8 +158,7 @@
|
|||||||
Import from IDS
|
Import from IDS
|
||||||
</DropdownMenu.SubTrigger>
|
</DropdownMenu.SubTrigger>
|
||||||
<DropdownMenu.SubContent class="w-64 max-h-64 overflow-y-auto">
|
<DropdownMenu.SubContent class="w-64 max-h-64 overflow-y-auto">
|
||||||
{#each Object.entries(IDS.Module.documents) as [docId, doc]}
|
{#each importableDocuments as [docId, doc], docIndex}
|
||||||
{#if docId !== IDS.Module.activeDocument && doc.specifications?.specification?.length > 0}
|
|
||||||
<DropdownMenu.Label class="font-medium text-xs text-muted-foreground px-2 py-1 truncate">
|
<DropdownMenu.Label class="font-medium text-xs text-muted-foreground px-2 py-1 truncate">
|
||||||
{doc.info?.title || 'Untitled Document'}
|
{doc.info?.title || 'Untitled Document'}
|
||||||
</DropdownMenu.Label>
|
</DropdownMenu.Label>
|
||||||
@@ -146,12 +173,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
{/each}
|
{/each}
|
||||||
{#if Object.entries(IDS.Module.documents).filter(([id, d]) => id !== IDS.Module.activeDocument && d.specifications?.specification?.length > 0).indexOf([docId, doc]) < Object.entries(IDS.Module.documents).filter(([id, d]) => id !== IDS.Module.activeDocument && d.specifications?.specification?.length > 0).length - 1}
|
{#if docIndex < importableDocuments.length - 1}
|
||||||
<DropdownMenu.Separator />
|
<DropdownMenu.Separator />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
{#if Object.entries(IDS.Module.documents).filter(([docId, doc]) => docId !== IDS.Module.activeDocument && doc.specifications?.specification?.length > 0).length === 0}
|
{#if importableDocuments.length === 0}
|
||||||
<DropdownMenu.Item disabled>
|
<DropdownMenu.Item disabled>
|
||||||
<span class="text-sm">No specifications available to import</span>
|
<span class="text-sm">No specifications available to import</span>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
@@ -162,16 +188,30 @@
|
|||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
</div>
|
</div>
|
||||||
<div class="specifications-list scrollbar">
|
<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-icon">ℹ️</span>
|
||||||
<span class="spec-name">IDS Information</span>
|
<span class="spec-name">IDS Information</span>
|
||||||
</div>
|
</div>
|
||||||
{#if activeDocument?.specifications?.specification}
|
{#if activeDocument?.specifications?.specification}
|
||||||
{#each activeDocument.specifications.specification as spec, index}
|
{#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-icon">📄</span>
|
||||||
<span class="spec-name">{spec["@name"] || "Specification " + (index + 1)}</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">
|
<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>
|
<path d="M18 6L6 18M6 6l12 12"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -192,10 +232,10 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<!-- Editor/Viewer Toggle -->
|
<!-- Editor/Viewer Toggle -->
|
||||||
<div class="view-mode-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
|
Editor
|
||||||
</button>
|
</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
|
Viewer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,18 +249,18 @@
|
|||||||
<div class="spec-header">
|
<div class="spec-header">
|
||||||
<h2>{activeSpecification ? activeSpecification["@name"] || "Specification" : "Specification"}</h2>
|
<h2>{activeSpecification ? activeSpecification["@name"] || "Specification" : "Specification"}</h2>
|
||||||
<div class="spec-tabs">
|
<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 === 'info'} onclick={() => updateActiveDocumentState({ 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 === 'applicability'} onclick={() => updateActiveDocumentState({ 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 === 'requirements'} onclick={() => updateActiveDocumentState({ activeTab: 'requirements' })}>Requirements</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if documentState?.activeTab === 'info'}
|
{#if documentState?.activeTab === 'info'}
|
||||||
<SpecificationEditor />
|
<SpecificationEditor />
|
||||||
{:else if documentState?.activeTab === 'applicability'}
|
{:else if documentState?.activeTab === 'applicability'}
|
||||||
<ApplicabilityPanel activeTab />
|
<ApplicabilityPanel />
|
||||||
{:else if documentState?.activeTab === 'requirements'}
|
{:else if documentState?.activeTab === 'requirements'}
|
||||||
<RequirementsPanel activeTab />
|
<RequirementsPanel />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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,95 @@
|
|||||||
|
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_skipped?: 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[];
|
||||||
|
applicable_entities?: AuditReportEntity[];
|
||||||
|
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 tailwindcss from '@tailwindcss/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||||
import path from "path";
|
import path from "node:path";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), svelte()],
|
plugins: [tailwindcss(), svelte()],
|
||||||
@@ -11,4 +11,4 @@ export default defineConfig({
|
|||||||
$src: path.resolve("./src"),
|
$src: path.resolve("./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user