mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-20 20:22:09 +00:00
Merge remote-tracking branch 'origin/v0.8.0' into datamodel-v1.0
This commit is contained in:
+68
-10
@@ -25,9 +25,19 @@ WEBAPP_BUILD_DIR := $(WEBAPP_DIR)/dist
|
||||
PYODIDE_DIR := $(WEBAPP_DIR)/public/pyodide
|
||||
PYODIDE_VERSION := 0.28.0
|
||||
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
|
||||
webapp-dev:
|
||||
webapp-dev: pyodide-download ifcopenshell-wasm-download webapp-stage-ifctester-wheel
|
||||
cd $(WEBAPP_DIR) && npm install
|
||||
cd $(WEBAPP_DIR) && npm run dev
|
||||
|
||||
.PHONY: pyodide-download
|
||||
@@ -62,8 +72,43 @@ pyodide-download:
|
||||
echo "Pyodide $(PYODIDE_VERSION) prepared in $(PYODIDE_DIR)"; \
|
||||
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
|
||||
webapp-build: pyodide-download
|
||||
webapp-build: pyodide-download ifcopenshell-wasm-download webapp-stage-ifctester-wheel
|
||||
cd $(WEBAPP_DIR) && npm install
|
||||
cd $(WEBAPP_DIR) && npm run build
|
||||
|
||||
@@ -76,23 +121,36 @@ clean:
|
||||
rm -rf $(WEBAPP_BUILD_DIR)
|
||||
rm -rf $(WEBAPP_DIR)/node_modules
|
||||
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 dist
|
||||
|
||||
.PHONY: dist
|
||||
dist: webapp-prepare
|
||||
.PHONY: python-dist
|
||||
python-dist:
|
||||
rm -rf dist
|
||||
# For some reason OS is not initalized when we call common.mk dist, which matters on Windows.
|
||||
# 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
|
||||
webapp-prepare: webapp-build
|
||||
rm -rf $(PACKAGE_NAME)/webapp/www/*
|
||||
mkdir -p $(PACKAGE_NAME)/webapp/www
|
||||
cp -r $(WEBAPP_BUILD_DIR)/* $(PACKAGE_NAME)/webapp/www/
|
||||
cp $(WEBAPP_DIR)/__init__.py $(PACKAGE_NAME)/webapp/__init__.py
|
||||
cp $(WEBAPP_DIR)/serve.py $(PACKAGE_NAME)/webapp/serve.py
|
||||
rm -rf $(PACKAGE_WEBAPP_WWW_DIR)
|
||||
mkdir -p $(PACKAGE_WEBAPP_WWW_DIR)
|
||||
cp -r $(WEBAPP_BUILD_DIR)/* $(PACKAGE_WEBAPP_WWW_DIR)/
|
||||
cp $(WEBAPP_DIR)/__init__.py $(PACKAGE_WEBAPP_DIR)/__init__.py
|
||||
cp $(WEBAPP_DIR)/serve.py $(PACKAGE_WEBAPP_DIR)/serve.py
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest -p no:pytest-blender test
|
||||
|
||||
.PHONY: build-ids-docs
|
||||
build-ids-docs:
|
||||
mkdir -p test/build
|
||||
cd test && python ids_doc_generator.py
|
||||
|
||||
@@ -19,3 +19,4 @@
|
||||
from .ids import open
|
||||
|
||||
__version__ = version = "0.0.0"
|
||||
__all__ = ["open"]
|
||||
|
||||
@@ -18,13 +18,14 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
import argparse
|
||||
import ifcopenshell
|
||||
from . import ids
|
||||
from . import reporter
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
from . import ids, reporter
|
||||
|
||||
parser = argparse.ArgumentParser(description="Uses an IDS to audit an IFC")
|
||||
parser.add_argument("ids", type=str, help="Path to an IDS")
|
||||
parser.add_argument("ifc", type=str, help="Path to an IFC", nargs="?")
|
||||
|
||||
@@ -17,15 +17,17 @@
|
||||
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import re
|
||||
|
||||
import builtins
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.classification
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from xmlschema.validators import identities
|
||||
from typing import Union, Optional, Any, Literal, TYPE_CHECKING, TypedDict
|
||||
from logging import Logger
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, Union
|
||||
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
from xmlschema.validators import identities
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .ids import Specification
|
||||
|
||||
@@ -17,29 +17,32 @@
|
||||
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
|
||||
import datetime
|
||||
import ifcopenshell
|
||||
from xmlschema.validators.exceptions import XMLSchemaValidationError
|
||||
from xmlschema import XMLSchema
|
||||
from xmlschema import etree_tostring
|
||||
import os
|
||||
from typing import Any, Optional, Union
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
import ifcopenshell
|
||||
from xmlschema import XMLSchema, etree_tostring
|
||||
from xmlschema.validators.exceptions import XMLSchemaValidationError
|
||||
|
||||
from .facet import (
|
||||
Facet,
|
||||
Entity,
|
||||
Attribute,
|
||||
Cardinality,
|
||||
Classification,
|
||||
Property,
|
||||
PartOf,
|
||||
Entity,
|
||||
Facet,
|
||||
FacetFailure,
|
||||
Material,
|
||||
PartOf,
|
||||
Property,
|
||||
Restriction,
|
||||
get_pset,
|
||||
get_psets,
|
||||
Cardinality,
|
||||
FacetFailure,
|
||||
)
|
||||
from typing import Optional, Union, Any
|
||||
|
||||
__all__ = ["Attribute", "Classification", "Entity", "Material", "PartOf", "Property", "Restriction"]
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
schema = None
|
||||
|
||||
@@ -298,23 +301,15 @@ class Specification:
|
||||
if not is_applicable:
|
||||
continue
|
||||
self.applicable_entities.append(element)
|
||||
for facet in self.requirements:
|
||||
result = facet(element)
|
||||
is_pass = bool(result)
|
||||
if self.maxOccurs != 0: # This is a required or optional specification
|
||||
if is_pass:
|
||||
if self.maxOccurs != 0: # Requirements are skipped for prohibited applicability
|
||||
for facet in self.requirements:
|
||||
result = facet(element)
|
||||
if bool(result):
|
||||
self.passed_entities.add(element)
|
||||
facet.passed_entities.add(element)
|
||||
else:
|
||||
self.failed_entities.add(element)
|
||||
facet.failures.append(FacetFailure(element=element, reason=str(result)))
|
||||
else: # This is a prohibited specification
|
||||
if is_pass:
|
||||
self.failed_entities.add(element)
|
||||
facet.failures.append(FacetFailure(element=element, reason=str(result)))
|
||||
else:
|
||||
self.passed_entities.add(element)
|
||||
facet.passed_entities.add(element)
|
||||
|
||||
self.status = True
|
||||
for facet in self.requirements:
|
||||
|
||||
@@ -17,18 +17,21 @@
|
||||
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import math
|
||||
import datetime
|
||||
from typing import Literal, Optional, TypedDict, Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
from .ids import Specification, Ids
|
||||
|
||||
from .facet import Facet, FacetFailure
|
||||
from typing import TypedDict, Union, Literal, Optional
|
||||
from .ids import Ids, Specification
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
@@ -78,10 +81,12 @@ class ResultsSpecification(TypedDict):
|
||||
description: str
|
||||
instructions: str
|
||||
status: bool
|
||||
is_skipped: bool
|
||||
is_ifc_version: bool
|
||||
total_applicable: int
|
||||
total_applicable_pass: int
|
||||
total_applicable_fail: int
|
||||
applicable_entities: list[ResultsEntity]
|
||||
percent_applicable_pass: ResultsPercent
|
||||
total_checks: int
|
||||
total_checks_pass: int
|
||||
@@ -364,16 +369,24 @@ class Json(Reporter):
|
||||
cardinality = "optional"
|
||||
elif specification.minOccurs == 0 and specification.maxOccurs == 0:
|
||||
cardinality = "prohibited"
|
||||
elif specification.minOccurs >= 1:
|
||||
# Any minimum occurrence >= 1 means the specification is required
|
||||
cardinality = "required"
|
||||
else:
|
||||
# minOccurs == 0 with any other maxOccurs value means optional
|
||||
cardinality = "optional"
|
||||
|
||||
return ResultsSpecification(
|
||||
name=specification.name,
|
||||
description=specification.description,
|
||||
instructions=specification.instructions,
|
||||
status=specification.status,
|
||||
is_skipped=cardinality == "optional" and total_checks == 0,
|
||||
is_ifc_version=specification.is_ifc_version,
|
||||
total_applicable=total_applicable,
|
||||
total_applicable_pass=total_applicable_pass,
|
||||
total_applicable_fail=total_applicable - total_applicable_pass,
|
||||
applicable_entities=self.report_applicable_entities(specification),
|
||||
percent_applicable_pass=percent_applicable_pass,
|
||||
total_checks=total_checks,
|
||||
total_checks_pass=total_checks_pass,
|
||||
@@ -384,6 +397,24 @@ class Json(Reporter):
|
||||
requirements=requirements,
|
||||
)
|
||||
|
||||
def report_applicable_entities(self, specification: Specification) -> list[ResultsEntity]:
|
||||
return [
|
||||
ResultsEntity(
|
||||
{
|
||||
"element": e,
|
||||
"element_type": ifcopenshell.util.element.get_type(e),
|
||||
"class": e.is_a(),
|
||||
"predefined_type": ifcopenshell.util.element.get_predefined_type(e),
|
||||
"name": getattr(e, "Name", None),
|
||||
"description": getattr(e, "Description", None),
|
||||
"id": e.id(),
|
||||
"global_id": getattr(e, "GlobalId", None),
|
||||
"tag": getattr(e, "Tag", None),
|
||||
}
|
||||
)
|
||||
for e in specification.applicable_entities
|
||||
]
|
||||
|
||||
def report_passed_entities(self, requirement: Facet) -> list[ResultsEntity]:
|
||||
return [
|
||||
ResultsEntity(
|
||||
@@ -445,11 +476,13 @@ class Html(Json):
|
||||
def report(self) -> None:
|
||||
super().report()
|
||||
for spec in self.results["specifications"]:
|
||||
if spec["cardinality"] == "optional" and spec["total_checks"] == 0:
|
||||
spec["is_skipped"] = True
|
||||
spec["is_prohibited"] = spec["cardinality"] == "prohibited"
|
||||
spec["cardinality"] = spec["cardinality"].capitalize()
|
||||
spec["has_requirements"] = bool(spec["requirements"])
|
||||
total_applicable_entities = len(spec["applicable_entities"])
|
||||
spec["applicable_entities"] = self.limit_entities(spec["applicable_entities"])
|
||||
spec["has_omitted_applicable"] = total_applicable_entities > self.entity_limit
|
||||
spec["total_omitted_applicable"] = total_applicable_entities - self.entity_limit
|
||||
for requirement in spec["requirements"]:
|
||||
total_passed_entities = len(requirement["passed_entities"])
|
||||
total_failed_entities = len(requirement["failed_entities"])
|
||||
@@ -555,7 +588,7 @@ class Ods(Json):
|
||||
def to_file(self, filepath: str) -> None:
|
||||
from odf.opendocument import OpenDocumentSpreadsheet
|
||||
from odf.style import Style, TableCellProperties
|
||||
from odf.table import Table, TableRow, TableCell
|
||||
from odf.table import Table, TableCell, TableRow
|
||||
from odf.text import P
|
||||
|
||||
self.doc = OpenDocumentSpreadsheet()
|
||||
@@ -696,7 +729,7 @@ class OdsSummary(Json):
|
||||
def to_file(self, filepath: str) -> None:
|
||||
from odf.opendocument import OpenDocumentSpreadsheet
|
||||
from odf.style import Style, TableCellProperties
|
||||
from odf.table import Table, TableRow, TableCell
|
||||
from odf.table import Table, TableCell, TableRow
|
||||
from odf.text import P
|
||||
|
||||
self.doc = OpenDocumentSpreadsheet()
|
||||
|
||||
@@ -152,7 +152,6 @@
|
||||
<p>
|
||||
<strong>Requirements</strong>
|
||||
</p>
|
||||
{{/has_requirements}}
|
||||
<ol>
|
||||
{{#requirements}}
|
||||
<li class="{{^total_checks}}skipped{{/total_checks}}{{#total_checks}}{{#status}}pass{{/status}}{{^status}}fail{{/status}}{{/total_checks}}">
|
||||
@@ -252,6 +251,45 @@
|
||||
</li>
|
||||
{{/requirements}}
|
||||
</ol>
|
||||
{{/has_requirements}}
|
||||
{{#is_prohibited}}
|
||||
{{#total_applicable}}
|
||||
<table class="fail">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Class</th>
|
||||
<th>PredefinedType</th>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>GlobalId</th>
|
||||
<th>Tag</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#applicable_entities}}
|
||||
<tr>
|
||||
<td>{{class}}</td>
|
||||
<td>{{predefined_type}}</td>
|
||||
<td>{{name}}</td>
|
||||
<td>{{description}}</td>
|
||||
<td>{{global_id}}</td>
|
||||
<td>{{tag}}</td>
|
||||
</tr>
|
||||
{{#extra_of_type}}
|
||||
<tr>
|
||||
<td colspan="7">... {{extra_of_type}} more of the same element type ({{type_name}} with Tag {{type_tag}} and GlobalId {{type_global_id}}) not shown ...</td>
|
||||
</tr>
|
||||
{{/extra_of_type}}
|
||||
{{/applicable_entities}}
|
||||
{{#has_omitted_applicable}}
|
||||
<tr>
|
||||
<td colspan="7"> ... {{total_omitted_applicable}} more failing elements not shown out of {{total_applicable}} total ...</td>
|
||||
</tr>
|
||||
{{/has_omitted_applicable}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/total_applicable}}
|
||||
{{/is_prohibited}}
|
||||
</div>
|
||||
</section>
|
||||
{{/specifications}}
|
||||
|
||||
@@ -32,3 +32,9 @@ exclude = ["test*"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"*" = ["*.*"]
|
||||
|
||||
[tool.ruff]
|
||||
extend = "../../pyproject.toml"
|
||||
lint.select = [
|
||||
"F401", # unused imports
|
||||
]
|
||||
|
||||
@@ -16,13 +16,24 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Documentation generator for IfcTester IDS facets and test cases.
|
||||
|
||||
This is not a test file. It lives in the test/ directory because it reuses
|
||||
test cases from test_facet.py and test_ids.py to generate example IFC files,
|
||||
IDS files, and Markdown documentation into test/build/.
|
||||
|
||||
Run via: make build-ids-docs (from src/ifctester/)
|
||||
"""
|
||||
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
import pytest
|
||||
import functools
|
||||
from pathlib import Path
|
||||
from xml.dom.minidom import parseString
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.geometry
|
||||
@@ -32,15 +43,16 @@ import ifcopenshell.api.spatial
|
||||
import ifcopenshell.api.type
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.guid
|
||||
import ifctester
|
||||
import numpy as np
|
||||
import pytest
|
||||
import test_facet
|
||||
import test_ids
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
from xml.dom.minidom import parseString
|
||||
from ifctester import ids
|
||||
from ifcopenshell import validate
|
||||
|
||||
import ifctester
|
||||
import ifctester.facet
|
||||
from ifctester import ids
|
||||
|
||||
outdir = "build"
|
||||
|
||||
|
||||
@@ -116,6 +128,8 @@ class FacetDocGenerator:
|
||||
{"name": name, "ids": xml_text, "ifc": ifc_text, "basename": basename, "result": result, "id": inst.id()}
|
||||
)
|
||||
|
||||
ifctester.facet.get_pset.cache_clear()
|
||||
ifctester.facet.get_psets.cache_clear()
|
||||
assert bool(facet(inst)) is expected
|
||||
|
||||
def set_facet(self, facet):
|
||||
@@ -139,7 +153,7 @@ class IdsDocGenerator:
|
||||
all_applicable.update(spec.applicable_entities)
|
||||
for requirement in spec.requirements:
|
||||
if requirement.status is False:
|
||||
all_failures.update(requirement.failed_entities)
|
||||
all_failures.update(f["element"] for f in requirement.failures)
|
||||
assert set(all_applicable) == set(applicable_entities)
|
||||
assert set(all_failures) == set(failed_entities)
|
||||
|
||||
@@ -150,7 +164,11 @@ class IdsDocGenerator:
|
||||
l = validate.json_logger()
|
||||
validate.validate(ifc, l)
|
||||
for issue in l.statements:
|
||||
raise Exception("About to emit invalid example data:", issue)
|
||||
# test_parsing_entities_with_no_attributes uses nameless IfcMaterial; fix for doc generation.
|
||||
if issue["instance"].is_a("IfcMaterial") and issue.get("attribute") == "IfcMaterial.Name":
|
||||
issue["instance"].Name = "Unnamed"
|
||||
else:
|
||||
raise Exception("About to emit invalid example data:", issue)
|
||||
|
||||
lines = ifc.to_string().split("\n")[7:-3]
|
||||
ifc_text = ""
|
||||
@@ -292,12 +310,12 @@ spec = ifctester.ids.Specification(
|
||||
)
|
||||
specs.specifications.append(spec)
|
||||
spec.applicability.append(ifctester.ids.Entity(name="IFCWALLTYPE"))
|
||||
restriction = ifctester.ids.Restriction(options={"pattern": "(-|[0-9]{2,3})\/(-|[0-9]{2,3})\/(-|[0-9]{2,3})"})
|
||||
restriction = ifctester.ids.Restriction(options={"pattern": r"(-|[0-9]{2,3})/(-|[0-9]{2,3})/(-|[0-9]{2,3})"})
|
||||
spec.requirements.append(
|
||||
ifctester.ids.Property(
|
||||
propertySet="Pset_WallCommon",
|
||||
name="FireRating",
|
||||
datatype="IfcLabel",
|
||||
baseName="FireRating",
|
||||
dataType="IfcLabel",
|
||||
value=restriction,
|
||||
instructions="Fire rating is specified using the Fire Resistance Level as defined in the Australian National Construction Code (NCC) 2019. Valid examples include -/-/-, -/120/120, and 60/60/60",
|
||||
)
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
|
||||
import uuid
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.classification
|
||||
import ifcopenshell.api.group
|
||||
@@ -33,9 +33,18 @@ import ifcopenshell.api.type
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.pset
|
||||
import ifctester.ids
|
||||
|
||||
import ifctester.facet
|
||||
from ifctester.facet import Entity, Attribute, Classification, Property, PartOf, Material, Restriction
|
||||
import ifctester.ids
|
||||
from ifctester.facet import (
|
||||
Attribute,
|
||||
Classification,
|
||||
Entity,
|
||||
Material,
|
||||
PartOf,
|
||||
Property,
|
||||
Restriction,
|
||||
)
|
||||
|
||||
|
||||
def set_facet(facet):
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.material
|
||||
import pytest
|
||||
import xmlschema
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.material
|
||||
|
||||
from ifctester import ids
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def run(
|
||||
|
||||
@@ -11,6 +11,8 @@ node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
public/worker/bin/ifcopenshell-*.whl
|
||||
public/worker/generated
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
@@ -24,4 +26,4 @@ dist-ssr
|
||||
*.sw?
|
||||
|
||||
.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>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
/**
|
||||
* svelte-preprocess cannot figure out whether you have
|
||||
* a value or a type, so tell TypeScript to enforce using
|
||||
* `import type` instead of `import` for Types.
|
||||
*/
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* To have warnings / errors of the Svelte compiler at the
|
||||
* correct position, enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable this if you'd like to use dynamic types.
|
||||
*/
|
||||
"checkJs": false,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"],
|
||||
"$src": ["./src"],
|
||||
"$src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Use global.d.ts instead of compilerOptions.types
|
||||
* to avoid limiting type declarations.
|
||||
*/
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
Generated
+482
-159
@@ -16,6 +16,7 @@
|
||||
"svelte-spa-router": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
@@ -24,19 +25,22 @@
|
||||
"clsx": "^2.1.1",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"svelte": "^5.28.1",
|
||||
"svelte": "^5.53.6",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"vite": "^6.4.1"
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
@@ -46,6 +50,170 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
|
||||
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
"biome": "bin/biome"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "1.9.4",
|
||||
"@biomejs/cli-darwin-x64": "1.9.4",
|
||||
"@biomejs/cli-linux-arm64": "1.9.4",
|
||||
"@biomejs/cli-linux-arm64-musl": "1.9.4",
|
||||
"@biomejs/cli-linux-x64": "1.9.4",
|
||||
"@biomejs/cli-linux-x64-musl": "1.9.4",
|
||||
"@biomejs/cli-win32-arm64": "1.9.4",
|
||||
"@biomejs/cli-win32-x64": "1.9.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
|
||||
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
|
||||
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.1.tgz",
|
||||
@@ -543,6 +711,16 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/remapping": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
@@ -588,9 +766,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz",
|
||||
"integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -602,9 +780,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz",
|
||||
"integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -616,9 +794,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz",
|
||||
"integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -630,9 +808,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz",
|
||||
"integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -644,9 +822,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz",
|
||||
"integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -658,9 +836,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz",
|
||||
"integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -672,9 +850,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz",
|
||||
"integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
||||
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -686,9 +864,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz",
|
||||
"integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
||||
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -700,9 +878,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -714,9 +892,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz",
|
||||
"integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -727,10 +905,10 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==",
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -741,10 +919,38 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==",
|
||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -756,9 +962,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -770,9 +976,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz",
|
||||
"integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -784,9 +990,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -798,9 +1004,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -812,9 +1018,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz",
|
||||
"integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -825,10 +1031,38 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz",
|
||||
"integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -840,9 +1074,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz",
|
||||
"integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -853,10 +1087,24 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz",
|
||||
"integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1270,9 +1518,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
@@ -1288,9 +1542,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
|
||||
"integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1381,6 +1635,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT/X11"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||
@@ -1411,7 +1681,6 @@
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@@ -1445,6 +1714,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.6.4",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
|
||||
"integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||
@@ -1546,9 +1821,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esrap": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz",
|
||||
"integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz",
|
||||
"integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
@@ -1639,9 +1914,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz",
|
||||
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
|
||||
"integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1955,9 +2230,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
||||
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
|
||||
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1967,22 +2242,6 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
||||
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "dist/cjs/src/bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/mode-watcher": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-1.1.0.tgz",
|
||||
@@ -2050,6 +2309,16 @@
|
||||
"svelte": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -2083,9 +2352,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2124,6 +2393,20 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/regexparam": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz",
|
||||
@@ -2134,13 +2417,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
|
||||
"integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -2150,26 +2433,31 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.41.1",
|
||||
"@rollup/rollup-android-arm64": "4.41.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.41.1",
|
||||
"@rollup/rollup-darwin-x64": "4.41.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.41.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.41.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.41.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.41.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.41.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.41.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.41.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.41.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.41.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.41.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
||||
"@rollup/rollup-android-arm64": "4.59.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.59.0",
|
||||
"@rollup/rollup-darwin-x64": "4.59.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.59.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
||||
"@rollup/rollup-openbsd-x64": "4.59.0",
|
||||
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -2200,6 +2488,19 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sade": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
||||
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mri": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded": {
|
||||
"version": "1.89.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.0.tgz",
|
||||
@@ -2618,35 +2919,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
|
||||
"integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
"debug": "~4.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2693,21 +2977,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.33.10",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.10.tgz",
|
||||
"integrity": "sha512-/yArPQIBoQS2p86LKnvJywOXkVHeEXnFgrDPSxkEfIAEkykopYuy2bF6UUqHG4IbZlJD6OurLxJT8Kn7kTk9WA==",
|
||||
"version": "5.53.6",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.6.tgz",
|
||||
"integrity": "sha512-lP5DGF3oDDI9fhHcSpaBiJEkFLuS16h92DhM1L5K1lFm0WjOmUh1i2sNkBBk8rkxJRpob0dBE75jRfUzGZUOGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
"@jridgewell/remapping": "^2.3.4",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||
"@types/estree": "^1.0.5",
|
||||
"@types/trusted-types": "^2.0.7",
|
||||
"acorn": "^8.12.1",
|
||||
"aria-query": "^5.3.1",
|
||||
"aria-query": "5.3.1",
|
||||
"axobject-query": "^4.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"devalue": "^5.6.3",
|
||||
"esm-env": "^1.2.1",
|
||||
"esrap": "^1.4.6",
|
||||
"esrap": "^2.2.2",
|
||||
"is-reference": "^3.0.3",
|
||||
"locate-character": "^3.0.0",
|
||||
"magic-string": "^0.30.11",
|
||||
@@ -2717,6 +3003,30 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz",
|
||||
"integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"chokidar": "^4.0.1",
|
||||
"fdir": "^6.2.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"sade": "^1.7.4"
|
||||
},
|
||||
"bin": {
|
||||
"svelte-check": "bin/svelte-check"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^4.0.0 || ^5.0.0-next.0",
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-sonner": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz",
|
||||
@@ -2867,17 +3177,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
||||
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
||||
"version": "7.5.11",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
|
||||
"integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"minizlib": "^3.0.1",
|
||||
"mkdirp": "^3.0.1",
|
||||
"minizlib": "^3.1.0",
|
||||
"yallist": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2918,6 +3227,20 @@
|
||||
"url": "https://github.com/sponsors/Wombosvideo"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
@@ -2941,9 +3264,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
|
||||
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"check": "tsc -p tsconfig.json --noEmit && svelte-check && biome lint .",
|
||||
"lint": "biome lint .",
|
||||
"lint:fix": "biome lint --write .",
|
||||
"preview": "vite preview",
|
||||
"deploy": "npm run build && npx wrangler pages deploy dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
@@ -18,13 +22,15 @@
|
||||
"clsx": "^2.1.1",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"svelte": "^5.28.1",
|
||||
"svelte": "^5.53.6",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"vite": "^6.4.1"
|
||||
"vite": "^6.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.pset
|
||||
import ifcopenshell.util.schema
|
||||
import xml.etree.ElementTree as ET
|
||||
from xmlschema.validators.exceptions import XMLSchemaValidationError
|
||||
|
||||
from ifctester.ids import Ids, IdsXmlValidationError, get_schema
|
||||
|
||||
# https://github.com/buildingSMART/IDS/blob/9914d568c7ac037acd97e58a0d16e9f93c3e3416/Schema/ids.xsd#L232
|
||||
|
||||
BIN
Binary file not shown.
@@ -16,20 +16,17 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import mimetypes
|
||||
import os
|
||||
import sys
|
||||
|
||||
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:
|
||||
sys.path.insert(0, bonsai_lib_path)
|
||||
|
||||
from flask import Flask, send_from_directory, send_file
|
||||
from flask import Flask, send_file, send_from_directory
|
||||
|
||||
# Browsers expecting this specific MIME type for .mjs files,
|
||||
# otherwise they fail to load.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Router from 'svelte-spa-router';
|
||||
import routes from './routes';
|
||||
</script>
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare module "*.svelte" {
|
||||
import type { SvelteComponent } from "svelte";
|
||||
export default class Component extends SvelteComponent {}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as Menubar from "$lib/components/ui/menubar";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import * as API from "$src/modules/api/api.svelte.js";
|
||||
import { error, success, info } from "$src/modules/utils/toast.svelte.js";
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import * as API from "$src/modules/api/api.svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
|
||||
let { isOpen = false } = $props();
|
||||
let { isOpen = false } : { isOpen?: boolean } = $props();
|
||||
|
||||
function openForum() {
|
||||
window.open('https://community.osarch.org', '_blank');
|
||||
@@ -23,8 +23,9 @@
|
||||
try {
|
||||
await IDS.openDocument();
|
||||
} catch (err) {
|
||||
if (err.message !== 'File selection cancelled') {
|
||||
error('Error opening file: ' + err.message);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (message !== 'File selection cancelled') {
|
||||
error(`Error opening file: ${message}`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
@@ -50,7 +51,7 @@
|
||||
success('Audit completed successfully');
|
||||
} catch (err) {
|
||||
console.error("Audit failed: ", err);
|
||||
error(`Audit failed: check console for details`);
|
||||
error("Audit failed: check console for details");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -142,4 +143,4 @@
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { Module } from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import { Module } from "$src/modules/api/ids.svelte";
|
||||
</script>
|
||||
|
||||
<div class="app-ribbon">
|
||||
@@ -20,4 +19,4 @@
|
||||
<span>Error</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as Tooltip from "$lib/components/ui/tooltip";
|
||||
import { IFCModels, loadIfc, unloadIfc, auditIfc, openIfc, createAuditReport, clearIdsAuditReports, runAudit } from "$src/modules/api/api.svelte.js";
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
||||
import { IFCModels, openIfc, unloadIfc, runAudit } from "$src/modules/api/api.svelte";
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
import { ChevronRightIcon, LinkIcon, XIcon } from "@lucide/svelte";
|
||||
import { Bonsai, connect, disconnect, runAudit as runBonsaiAudit } from "$src/modules/api/bonsai.svelte.js";
|
||||
import { Bonsai, connect, disconnect, runAudit as runBonsaiAudit } from "$src/modules/api/bonsai.svelte";
|
||||
import { onMount } from 'svelte';
|
||||
import type { AuditReport } from "$src/types/report";
|
||||
|
||||
let isAuditing = $state(false);
|
||||
let activeTab = $state('home');
|
||||
let activeTab = $state<"home" | "bonsai">('home');
|
||||
// biome-ignore lint/style/useConst: Svelte state uses assignment for updates.
|
||||
let isMinimized = $state(false);
|
||||
|
||||
const handleLoadModel = async () => {
|
||||
@@ -16,15 +18,17 @@
|
||||
await openIfc();
|
||||
success('IFC model loaded successfully');
|
||||
} catch (err) {
|
||||
error(`Failed to load IFC model: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to load IFC model: ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnloadModel = async (modelId) => {
|
||||
const handleUnloadModel = async (modelId: string) => {
|
||||
try {
|
||||
await unloadIfc(modelId);
|
||||
} catch (err) {
|
||||
error(`Failed to unload model: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to unload model: ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,14 +39,14 @@
|
||||
success('Audit completed successfully');
|
||||
} catch (err) {
|
||||
console.error("Audit failed: ", err);
|
||||
error(`Audit failed: check console for details`);
|
||||
error("Audit failed: check console for details");
|
||||
} finally {
|
||||
isAuditing = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewAuditReport = (auditId) => {
|
||||
const auditReport = IFCModels.audits.find(audit => audit.id === auditId);
|
||||
const handleViewAuditReport = (auditId: string) => {
|
||||
const auditReport = IFCModels.audits.find(audit => audit.id === auditId) as AuditReport | undefined;
|
||||
if (!auditReport) return;
|
||||
|
||||
// Switch to the IDS document that was used for this audit
|
||||
@@ -58,7 +62,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
const formatFileSize = (bytes: number) => {
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
let size = bytes;
|
||||
let unitIndex = 0;
|
||||
@@ -91,7 +95,7 @@
|
||||
<div class="buttons">
|
||||
<Tooltip.Provider>
|
||||
{#if isMinimized}
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn expand-btn" onclick={() => isMinimized = false} aria-label="Expand Toolbar">
|
||||
<ChevronRightIcon size={24} />
|
||||
@@ -102,7 +106,7 @@
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn {activeTab === 'home' ? 'active' : ''}" onclick={() => activeTab = 'home'} aria-label="Home">
|
||||
<svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
@@ -114,7 +118,7 @@
|
||||
<p>Home</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn {activeTab === 'bonsai' ? 'active' : ''}" onclick={() => activeTab = 'bonsai'} aria-label="Bonsai Integration">
|
||||
<svg style="height: 20px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32mm" height="32mm" version="1.1" viewBox="0 0 32 32" xml:space="preserve">
|
||||
@@ -692,4 +696,4 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
|
||||
let { addFacet } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
const { addFacet } : { addFacet: (facetType: FacetType) => void } = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -63,4 +65,4 @@
|
||||
Part Of
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import type { IdsDocument } from "$src/types/ids";
|
||||
|
||||
function switchDocument(docId) {
|
||||
function switchDocument(docId: string) {
|
||||
IDS.Module.activeDocument = docId;
|
||||
}
|
||||
|
||||
function closeDocument(docId) {
|
||||
function closeDocument(docId: string) {
|
||||
IDS.deleteDocument(docId);
|
||||
}
|
||||
|
||||
const handleActivation = (event: KeyboardEvent, action: () => void) => {
|
||||
if (event.currentTarget !== event.target) return;
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
action();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="ids-tabs">
|
||||
@@ -15,10 +24,13 @@
|
||||
<div
|
||||
class="ids-tab"
|
||||
class:active={IDS.Module.activeDocument === docId}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => switchDocument(docId)}
|
||||
aria-label={doc.info.title || "Untitled"}
|
||||
onkeydown={(event) => handleActivation(event, () => switchDocument(docId))}
|
||||
aria-label={(doc as IdsDocument).info.title || "Untitled"}
|
||||
>
|
||||
<span class="tab-title">{doc.info.title || "Untitled"}</span>
|
||||
<span class="tab-title">{(doc as IdsDocument).info.title || "Untitled"}</span>
|
||||
<button
|
||||
class="tab-close"
|
||||
onclick={(e) => { e.stopPropagation(); closeDocument(docId); }}
|
||||
@@ -31,4 +43,4 @@
|
||||
</div>
|
||||
{/each}
|
||||
<div class="filler-tab"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"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",
|
||||
"api_py_url": "/worker/api.py",
|
||||
"pyodide_url": "https://cdn.jsdelivr.net/pyodide/v0.28.0/full/pyodide.js"
|
||||
"api_py_url": "/worker/api.py"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +576,8 @@ html, body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
label,
|
||||
.form-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import XIcon from "@lucide/svelte/icons/x";
|
||||
import * as Dialog from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import * as Dialog from "./index";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
portalProps?: Record<string, unknown>;
|
||||
children?: Snippet;
|
||||
showCloseButton?: boolean;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -11,7 +20,7 @@
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Portal {...portalProps}>
|
||||
@@ -35,4 +44,4 @@
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</DialogPrimitive.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Portal>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Description
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dialog-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -15,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -16,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Overlay
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Title
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dialog-title"
|
||||
class={cn("text-lg leading-none", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
|
||||
+12
-4
@@ -1,8 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
checked?: boolean;
|
||||
indeterminate?: boolean;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
@@ -10,7 +18,7 @@
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
@@ -34,4 +42,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
|
||||
+11
-4
@@ -1,14 +1,21 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
sideOffset?: number;
|
||||
portalProps?: Record<string, unknown>;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||
@@ -22,4 +29,4 @@
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.GroupHeading
|
||||
@@ -15,4 +20,4 @@
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
variant?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
@@ -21,4 +28,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,12 +1,20 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -17,4 +25,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+8
-3
@@ -1,11 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
value?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
@@ -13,4 +18,4 @@
|
||||
bind:value
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,14 +1,22 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
value: string;
|
||||
children?: Snippet<[ { checked: boolean } ]>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
@@ -28,4 +36,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+11
-4
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
@@ -16,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,7 +1,15 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -9,7 +17,7 @@
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
@@ -24,4 +32,4 @@
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRightIcon class="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
|
||||
+7
-3
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
||||
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
checked?: boolean;
|
||||
indeterminate?: boolean;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
@@ -10,7 +18,7 @@
|
||||
indeterminate = $bindable(false),
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
@@ -34,4 +42,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Align = "start" | "center" | "end";
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
sideOffset?: number;
|
||||
alignOffset?: number;
|
||||
align?: Align;
|
||||
side?: Side;
|
||||
portalProps?: Record<string, unknown>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -11,7 +24,7 @@
|
||||
side = "bottom",
|
||||
portalProps,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Portal {...portalProps}>
|
||||
@@ -28,4 +41,4 @@
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
</MenubarPrimitive.Portal>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
inset,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.GroupHeading
|
||||
@@ -15,4 +20,4 @@
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Group bind:ref data-slot="menubar-group" {...restProps} />
|
||||
<MenubarPrimitive.Group bind:ref data-slot="menubar-group" {...restProps} />
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
variant?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -8,7 +15,7 @@
|
||||
inset = undefined,
|
||||
variant = "default",
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Item
|
||||
@@ -21,4 +28,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
inset,
|
||||
children,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -17,4 +24,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
value: string;
|
||||
children?: Snippet<[ { checked: boolean } ]>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.RadioItem
|
||||
@@ -28,4 +36,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.RadioItem>
|
||||
</MenubarPrimitive.RadioItem>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Separator
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="menubar-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
@@ -16,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.SubContent
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -9,7 +17,7 @@
|
||||
inset = undefined,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.SubTrigger
|
||||
@@ -24,4 +32,4 @@
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRightIcon class="ml-auto size-4" />
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Trigger
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Root
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Toaster as Sonner } from "svelte-sonner";
|
||||
import { mode } from "mode-watcher";
|
||||
|
||||
let { ...restProps } = $props();
|
||||
let { ...restProps } : Record<string, unknown> = $props();
|
||||
</script>
|
||||
|
||||
<Sonner
|
||||
@@ -10,4 +10,4 @@
|
||||
class="toaster group"
|
||||
style="--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border);"
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
sideOffset?: number;
|
||||
side?: Side;
|
||||
children?: Snippet;
|
||||
arrowClasses?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -10,7 +22,7 @@
|
||||
children,
|
||||
arrowClasses,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Portal>
|
||||
@@ -42,4 +54,4 @@
|
||||
{/snippet}
|
||||
</TooltipPrimitive.Arrow>
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
</TooltipPrimitive.Portal>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
|
||||
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { clsx, } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -0,0 +1,7 @@
|
||||
import { clsx } from "clsx";
|
||||
import type { ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { mount } from 'svelte';
|
||||
import './css/app.css';
|
||||
import App from './App.svelte';
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('root'),
|
||||
});
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { mount } from 'svelte';
|
||||
import './css/app.css';
|
||||
import App from './App.svelte';
|
||||
|
||||
const root = document.getElementById('root');
|
||||
if (!root) {
|
||||
throw new Error('Missing root element');
|
||||
}
|
||||
|
||||
const app = mount(App, {
|
||||
target: root,
|
||||
});
|
||||
|
||||
export default app;
|
||||
+76
-37
@@ -1,8 +1,31 @@
|
||||
import wasm from "$src/modules/wasm";
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import hyperid from "hyperid";
|
||||
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||
import type { IdsDocument } from "$src/types/ids";
|
||||
|
||||
export let Autocompletions = $state({
|
||||
type AutocompletionState = {
|
||||
entityClasses: string[];
|
||||
materialCategories: string[];
|
||||
classificationSystems: Record<string, { source: string; tokens: string[] | null }>;
|
||||
dataTypes: string[];
|
||||
isLoaded: boolean;
|
||||
};
|
||||
|
||||
type IfcModel = {
|
||||
id: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
loadedAt: Date;
|
||||
};
|
||||
|
||||
type IfcModelState = {
|
||||
models: IfcModel[];
|
||||
isLoading: boolean;
|
||||
audits: AuditReport[];
|
||||
};
|
||||
|
||||
export const Autocompletions: AutocompletionState = $state({
|
||||
entityClasses: [],
|
||||
materialCategories: [],
|
||||
classificationSystems: {},
|
||||
@@ -10,13 +33,13 @@ export let Autocompletions = $state({
|
||||
isLoaded: false
|
||||
});
|
||||
|
||||
export let IFCModels = $state({
|
||||
export const IFCModels: IfcModelState = $state({
|
||||
models: [],
|
||||
isLoading: false,
|
||||
audits: []
|
||||
});
|
||||
|
||||
const id = hyperid();
|
||||
const id: () => string = hyperid();
|
||||
|
||||
// Preload autocompletions on initialization
|
||||
wasm.init().then(async () => {
|
||||
@@ -30,26 +53,30 @@ export async function preloadAutocompletions() {
|
||||
// Entity classes
|
||||
const entitySets = await Promise.all(
|
||||
schemas.map(schema => wasm.getAllEntityClasses(schema))
|
||||
);
|
||||
const allEntities = new Set();
|
||||
entitySets.forEach(entities => {
|
||||
entities.forEach(entity => allEntities.add(entity.toUpperCase()));
|
||||
});
|
||||
) as string[][];
|
||||
const allEntities = new Set<string>();
|
||||
for (const entities of entitySets) {
|
||||
for (const entity of entities) {
|
||||
allEntities.add(entity.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
// Data types
|
||||
const dataTypeSets = await Promise.all(
|
||||
schemas.map(schema => wasm.getAllDataTypes(schema))
|
||||
);
|
||||
const allDataTypes = new Set();
|
||||
dataTypeSets.forEach(dataTypes => {
|
||||
Object.keys(dataTypes).forEach(dataType => allDataTypes.add(dataType));
|
||||
});
|
||||
) as Record<string, string>[];
|
||||
const allDataTypes = new Set<string>();
|
||||
for (const dataTypes of dataTypeSets) {
|
||||
for (const dataType of Object.keys(dataTypes as Record<string, string>)) {
|
||||
allDataTypes.add(dataType);
|
||||
}
|
||||
}
|
||||
|
||||
// Material categories and Classification systems
|
||||
const [materialCategories, classificationSystems] = await Promise.all([
|
||||
wasm.getMaterialCategories(),
|
||||
wasm.getStandardClassificationSystems()
|
||||
]);
|
||||
]) as [string[], AutocompletionState["classificationSystems"]];
|
||||
|
||||
// Cache autocompletions
|
||||
Autocompletions.entityClasses = Array.from(allEntities).sort();
|
||||
@@ -64,15 +91,15 @@ export async function preloadAutocompletions() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPredefinedTypes(schema, entity) {
|
||||
export async function getPredefinedTypes(schema: string, entity: string) {
|
||||
return await wasm.getPredefinedTypes(schema, entity);
|
||||
}
|
||||
|
||||
export async function getEntityAttributes(schema, entity) {
|
||||
export async function getEntityAttributes(schema: string, entity: string) {
|
||||
return await wasm.getEntityAttributes(schema, entity);
|
||||
}
|
||||
|
||||
export async function getApplicablePsets(schema, entity, predefinedType = '') {
|
||||
export async function getApplicablePsets(schema: string, entity: string, predefinedType = '') {
|
||||
return await wasm.getApplicablePsets(schema, entity, predefinedType);
|
||||
}
|
||||
|
||||
@@ -92,7 +119,7 @@ export function getDataTypes() {
|
||||
return Autocompletions.dataTypes;
|
||||
}
|
||||
|
||||
export async function loadIfc(file) {
|
||||
export async function loadIfc(file: File): Promise<IfcModel> {
|
||||
try {
|
||||
IFCModels.isLoading = true;
|
||||
|
||||
@@ -100,10 +127,10 @@ export async function loadIfc(file) {
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
// Load IFC model
|
||||
const ifcId = await wasm.loadIfc(Array.from(uint8Array));
|
||||
const ifcId = await wasm.loadIfc(Array.from(uint8Array)) as string;
|
||||
|
||||
// Add to models list
|
||||
const model = {
|
||||
const model: IfcModel = {
|
||||
id: ifcId,
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
@@ -121,7 +148,7 @@ export async function loadIfc(file) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function unloadIfc(modelId) {
|
||||
export async function unloadIfc(modelId: string) {
|
||||
try {
|
||||
// Unload model
|
||||
await wasm.unloadIfc(modelId);
|
||||
@@ -136,9 +163,9 @@ export async function unloadIfc(modelId) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function auditIfc(modelId, idsData) {
|
||||
export async function auditIfc(modelId: string, idsData: string | Uint8Array | ArrayBuffer) {
|
||||
try {
|
||||
let idsBytes;
|
||||
let idsBytes: Uint8Array;
|
||||
if (typeof idsData === 'string') {
|
||||
idsBytes = new TextEncoder().encode(idsData);
|
||||
} else if (idsData instanceof ArrayBuffer) {
|
||||
@@ -148,7 +175,7 @@ export async function auditIfc(modelId, idsData) {
|
||||
}
|
||||
|
||||
// Run audit
|
||||
const auditResult = await wasm.auditIfc(modelId, idsBytes);
|
||||
const auditResult = await wasm.auditIfc(modelId, idsBytes) as { json: AuditReportData; html: string };
|
||||
|
||||
console.log(`Audit completed for model ${modelId}`);
|
||||
return auditResult;
|
||||
@@ -163,13 +190,14 @@ export function getLoadedModels() {
|
||||
}
|
||||
|
||||
export async function openIfc() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.ifc';
|
||||
|
||||
fileInput.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
const target = event.target as HTMLInputElement | null;
|
||||
const file = target?.files?.[0];
|
||||
if (!file) {
|
||||
reject(new Error('No file selected'));
|
||||
return;
|
||||
@@ -194,15 +222,20 @@ export async function openIfc() {
|
||||
});
|
||||
}
|
||||
|
||||
export function getIfcById(modelId) {
|
||||
export function getIfcById(modelId: string) {
|
||||
return IFCModels.models.find(model => model.id === modelId);
|
||||
}
|
||||
|
||||
export function createAuditReport(modelId, document, auditData, htmlReport = null) {
|
||||
export function createAuditReport(
|
||||
modelId: string,
|
||||
document: string,
|
||||
auditData: AuditReportData,
|
||||
htmlReport: string | null = null
|
||||
): AuditReport | undefined {
|
||||
const model = getIfcById(modelId);
|
||||
if (!model) return;
|
||||
|
||||
const auditReport = {
|
||||
const auditReport: AuditReport = {
|
||||
id: id(),
|
||||
modelId: modelId,
|
||||
modelName: model.fileName,
|
||||
@@ -216,19 +249,19 @@ export function createAuditReport(modelId, document, auditData, htmlReport = nul
|
||||
return auditReport;
|
||||
}
|
||||
|
||||
export function getAuditReportsForIfc(modelId) {
|
||||
export function getAuditReportsForIfc(modelId: string) {
|
||||
return IFCModels.audits.filter(audit => audit.modelId === modelId);
|
||||
}
|
||||
|
||||
export function getAuditReportById(auditId) {
|
||||
export function getAuditReportById(auditId: string) {
|
||||
return IFCModels.audits.find(audit => audit.id === auditId);
|
||||
}
|
||||
|
||||
export function clearIdsAuditReports(document) {
|
||||
export function clearIdsAuditReports(document: string) {
|
||||
IFCModels.audits = IFCModels.audits.filter(audit => audit.document !== document);
|
||||
}
|
||||
|
||||
export async function downloadAuditReport(auditId) {
|
||||
export async function downloadAuditReport(auditId: string) {
|
||||
const audit = getAuditReportById(auditId);
|
||||
if (!audit || !audit.htmlReport) {
|
||||
throw new Error('HTML report not available for this audit');
|
||||
@@ -237,7 +270,7 @@ export async function downloadAuditReport(auditId) {
|
||||
// Get IDS document title for filename
|
||||
let filename = 'report.html';
|
||||
if (audit.document && IDS.Module.documents[audit.document]) {
|
||||
const doc = IDS.Module.documents[audit.document];
|
||||
const doc = IDS.Module.documents[audit.document] as IdsDocument;
|
||||
const title = doc.info?.title || 'untitled';
|
||||
filename = `report_${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.html`;
|
||||
}
|
||||
@@ -270,9 +303,12 @@ export async function runAudit() {
|
||||
|
||||
// Get the active IDS document XML
|
||||
const idsXml = await IDS.exportActiveDocument();
|
||||
if (!idsXml) {
|
||||
throw new Error('Failed to export IDS document');
|
||||
}
|
||||
|
||||
// Run audit on all loaded models
|
||||
let firstAuditReport = null;
|
||||
let firstAuditReport: AuditReport | undefined;
|
||||
for (const model of IFCModels.models) {
|
||||
const result = await auditIfc(model.id, idsXml);
|
||||
|
||||
@@ -280,7 +316,10 @@ export async function runAudit() {
|
||||
const jsonData = result.json || null;
|
||||
const htmlReport = result.html || null;
|
||||
|
||||
const auditReport = createAuditReport(model.id, IDS.Module.activeDocument, jsonData, htmlReport);
|
||||
if (!jsonData) {
|
||||
continue;
|
||||
}
|
||||
const auditReport = createAuditReport(model.id, IDS.Module.activeDocument as string, jsonData, htmlReport);
|
||||
|
||||
// Store the first audit report to open in viewer
|
||||
if (!firstAuditReport) {
|
||||
+60
-22
@@ -1,12 +1,37 @@
|
||||
import { io } from 'socket.io-client';
|
||||
import { IFCModels } from './api.svelte.js';
|
||||
import * as IDS from './ids.svelte.js';
|
||||
import { error, success } from '../utils/toast.svelte.js';
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import { IFCModels } from './api.svelte';
|
||||
import * as IDS from './ids.svelte';
|
||||
import { error, success } from '../utils/toast.svelte';
|
||||
import hyperid from 'hyperid';
|
||||
import { onMount } from 'svelte';
|
||||
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||
|
||||
// Bonsai connection state
|
||||
export let Bonsai = $state({
|
||||
type BonsaiState = {
|
||||
enabled: boolean;
|
||||
port: string | null;
|
||||
socket: Socket | null;
|
||||
connected: boolean;
|
||||
auditing: boolean;
|
||||
};
|
||||
|
||||
type PendingAudit = {
|
||||
resolve: (value: string | null) => void;
|
||||
reject: (reason?: unknown) => void;
|
||||
};
|
||||
|
||||
type AuditResultPayload = {
|
||||
id?: string;
|
||||
json_report?: string;
|
||||
html_report?: string;
|
||||
};
|
||||
|
||||
type AuditErrorPayload = {
|
||||
id?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export const Bonsai: BonsaiState = $state({
|
||||
enabled: false,
|
||||
port: null,
|
||||
socket: null,
|
||||
@@ -14,8 +39,8 @@ export let Bonsai = $state({
|
||||
auditing: false
|
||||
});
|
||||
|
||||
const id = hyperid();
|
||||
const pendingAudits = new Map();
|
||||
const id: () => string = hyperid();
|
||||
const pendingAudits = new Map<string, PendingAudit>();
|
||||
|
||||
// Check for Bonsai server port in URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -29,8 +54,11 @@ if (serverPort) {
|
||||
/**
|
||||
* Connect to Bonsai server
|
||||
*/
|
||||
export const connect = () => new Promise((resolve, reject) => {
|
||||
if (!Bonsai.port) return;
|
||||
export const connect = () => new Promise<void>((resolve, reject) => {
|
||||
if (!Bonsai.port) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Bonsai.socket = io(`ws://127.0.0.1:${Bonsai.port}/ifctester`, {
|
||||
@@ -49,7 +77,7 @@ export const connect = () => new Promise((resolve, reject) => {
|
||||
Bonsai.connected = false;
|
||||
});
|
||||
|
||||
Bonsai.socket.on('connect_error', (err) => {
|
||||
Bonsai.socket.on('connect_error', (err: Error) => {
|
||||
Bonsai.connected = false;
|
||||
error(`Failed to connect to Bonsai: ${err.message}`);
|
||||
reject(err);
|
||||
@@ -59,7 +87,8 @@ export const connect = () => new Promise((resolve, reject) => {
|
||||
Bonsai.socket.on('error', handleAuditError);
|
||||
|
||||
} catch (err) {
|
||||
error(`Failed to connect to Bonsai: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to connect to Bonsai: ${message}`);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
@@ -93,14 +122,21 @@ export const runAudit = async () => {
|
||||
|
||||
// Convert IDS document to XML string
|
||||
const idsXml = await IDS.exportActiveDocument();
|
||||
if (!idsXml) {
|
||||
throw new Error('Failed to export IDS document');
|
||||
}
|
||||
|
||||
const requestId = id();
|
||||
const socket = Bonsai.socket;
|
||||
if (!socket) {
|
||||
throw new Error('Bonsai socket not connected');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<string | null>((resolve, reject) => {
|
||||
// Store request with resolve/reject functions
|
||||
pendingAudits.set(requestId, { resolve, reject });
|
||||
|
||||
Bonsai.socket.emit('audit_ids', {
|
||||
socket.emit('audit_ids', {
|
||||
id: requestId,
|
||||
ids: idsXml
|
||||
});
|
||||
@@ -108,7 +144,8 @@ export const runAudit = async () => {
|
||||
|
||||
} catch (err) {
|
||||
Bonsai.auditing = false;
|
||||
error(`Failed to run Bonsai audit: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to run Bonsai audit: ${message}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -117,7 +154,7 @@ export const runAudit = async () => {
|
||||
* Handles audit results from Bonsai server
|
||||
* @param {Object} data - Audit result data
|
||||
*/
|
||||
const handleAuditResult = (data) => {
|
||||
const handleAuditResult = (data: AuditResultPayload) => {
|
||||
if (!data.id || !data.json_report) return;
|
||||
|
||||
const pendingAudit = pendingAudits.get(data.id);
|
||||
@@ -130,13 +167,14 @@ const handleAuditResult = (data) => {
|
||||
const { resolve } = pendingAudit;
|
||||
|
||||
try {
|
||||
const reportData = JSON.parse(data.json_report);
|
||||
const reportData = JSON.parse(data.json_report) as AuditReportData;
|
||||
|
||||
const auditReport = {
|
||||
const auditReport: AuditReport = {
|
||||
id: data.id,
|
||||
modelId: `bonsai:${data.id}`,
|
||||
date: new Date().toISOString(),
|
||||
modelName: 'Bonsai IFC Model',
|
||||
document: IDS.Module.activeDocument,
|
||||
document: IDS.Module.activeDocument ?? "",
|
||||
data: reportData,
|
||||
htmlReport: data.html_report
|
||||
};
|
||||
@@ -152,7 +190,8 @@ const handleAuditResult = (data) => {
|
||||
|
||||
} catch (err) {
|
||||
Bonsai.auditing = false;
|
||||
error(`Failed to process audit result: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to process audit result: ${message}`);
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
@@ -161,7 +200,7 @@ const handleAuditResult = (data) => {
|
||||
* Handles audit errors from Bonsai server
|
||||
* @param {Object} data - Error data
|
||||
*/
|
||||
const handleAuditError = (data) => {
|
||||
const handleAuditError = (data: AuditErrorPayload) => {
|
||||
if (!data.id) return;
|
||||
|
||||
const pendingAudit = pendingAudits.get(data.id);
|
||||
@@ -174,7 +213,6 @@ const handleAuditError = (data) => {
|
||||
const { resolve } = pendingAudit;
|
||||
|
||||
Bonsai.auditing = false;
|
||||
error(`Audit failed (Bonsai): ${data.error}`);
|
||||
error(`Audit failed (Bonsai): ${data.error ?? "Unknown error"}`);
|
||||
resolve(null);
|
||||
};
|
||||
|
||||
+129
-90
@@ -1,10 +1,18 @@
|
||||
import wasm from "$src/modules/wasm";
|
||||
import { clearIdsAuditReports } from "./api.svelte.js";
|
||||
import { clearIdsAuditReports } from "./api.svelte";
|
||||
import hyperid from "hyperid";
|
||||
import {tick} from "svelte";
|
||||
import type { DocumentState, Facet, FacetValue, IdsDocument, IdsCardinality, Restriction, Specification } from "$src/types/ids";
|
||||
|
||||
export let Module = $state({
|
||||
documents: [],
|
||||
type ModuleState = {
|
||||
documents: Record<string, IdsDocument>;
|
||||
activeDocument: string | null;
|
||||
status: "loading" | "ready" | "error";
|
||||
states: Record<string, DocumentState>;
|
||||
};
|
||||
|
||||
export const Module: ModuleState = $state({
|
||||
documents: {},
|
||||
activeDocument: null,
|
||||
status: "loading",
|
||||
states: {}
|
||||
@@ -17,14 +25,15 @@ wasm.init().then(() => {
|
||||
Module.status = "error";
|
||||
});
|
||||
|
||||
const id = hyperid()
|
||||
const id: () => string = hyperid();
|
||||
|
||||
export function setDocumentState(docId, updates) {
|
||||
export function setDocumentState(docId: string, updates: Partial<DocumentState>) {
|
||||
if (!Module.states[docId]) {
|
||||
Module.states[docId] = {
|
||||
activeTab: 'info',
|
||||
viewMode: 'editor',
|
||||
activeSpecification: null
|
||||
activeSpecification: null,
|
||||
auditReport: null
|
||||
};
|
||||
}
|
||||
Object.assign(Module.states[docId], updates);
|
||||
@@ -32,7 +41,7 @@ export function setDocumentState(docId, updates) {
|
||||
|
||||
export async function createDocument() {
|
||||
const docId = id();
|
||||
const doc = await wasm.createIDS();
|
||||
const doc = await wasm.createIDS() as IdsDocument;
|
||||
|
||||
Module.documents[docId] = doc;
|
||||
|
||||
@@ -43,14 +52,14 @@ export async function createDocument() {
|
||||
Module.activeDocument = docId;
|
||||
}
|
||||
|
||||
export async function deleteDocument(id) {
|
||||
export async function deleteDocument(id: string) {
|
||||
// Clear any audit reports generated using this IDS document
|
||||
clearIdsAuditReports(id);
|
||||
|
||||
delete Module.documents[id];
|
||||
delete Module.states[id];
|
||||
|
||||
if (Module.activeDocument == id) {
|
||||
if (Module.activeDocument === id) {
|
||||
// If there are other documents, set the first one as active
|
||||
if (Object.keys(Module.documents).length > 0) {
|
||||
Module.activeDocument = Object.keys(Module.documents)[0];
|
||||
@@ -62,19 +71,19 @@ export async function deleteDocument(id) {
|
||||
|
||||
// Normalize (remove xs: prefix) from JSON dict returned from Python
|
||||
// We need this because the backend exports with xs: prefix, yet expects a dict without prefixes.
|
||||
function normalizeIdsDict(obj) {
|
||||
function normalizeIdsDict(obj: unknown): unknown {
|
||||
if (typeof obj !== 'object' || obj === null) return obj;
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(normalizeIdsDict);
|
||||
}
|
||||
|
||||
const result = {};
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (key === 'xs:restriction' && Array.isArray(value) && value.length > 0) {
|
||||
// Convert xs:restriction array to restriction object
|
||||
const restriction = value[0];
|
||||
const newRestriction = {};
|
||||
const restriction = value[0] as Record<string, unknown>;
|
||||
const newRestriction: Record<string, unknown> = {};
|
||||
|
||||
for (const [restrictionKey, restrictionValue] of Object.entries(restriction)) {
|
||||
if (restrictionKey.startsWith('xs:')) {
|
||||
@@ -86,7 +95,7 @@ function normalizeIdsDict(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
result['restriction'] = newRestriction;
|
||||
result.restriction = newRestriction;
|
||||
} else {
|
||||
result[key] = normalizeIdsDict(value);
|
||||
}
|
||||
@@ -96,13 +105,16 @@ function normalizeIdsDict(obj) {
|
||||
}
|
||||
|
||||
export async function openDocument() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileInput = document.createElement('input');
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const fileInput = document.createElement('input') as HTMLInputElement & {
|
||||
oncancel?: ((this: HTMLInputElement, ev: Event) => void) | null;
|
||||
};
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.ids,.xml';
|
||||
|
||||
fileInput.onchange = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
const target = event.target as HTMLInputElement | null;
|
||||
const file = target?.files?.[0];
|
||||
if (!file) {
|
||||
reject(new Error('No file selected'));
|
||||
return;
|
||||
@@ -112,8 +124,8 @@ export async function openDocument() {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
const fileContent = e.target.result;
|
||||
const doc = normalizeIdsDict(await wasm.openIDS(fileContent, false));
|
||||
const fileContent = (e.target as FileReader).result;
|
||||
const doc = normalizeIdsDict(await wasm.openIDS(String(fileContent), false)) as IdsDocument;
|
||||
const docId = id();
|
||||
|
||||
// Add document to list and set as active
|
||||
@@ -145,16 +157,16 @@ export async function openDocument() {
|
||||
});
|
||||
}
|
||||
|
||||
export async function exportActiveDocument() {
|
||||
export async function exportActiveDocument(): Promise<string | null> {
|
||||
if (!Module.activeDocument) return null;
|
||||
|
||||
const doc = $state.snapshot(Module.documents[Module.activeDocument]);
|
||||
const xmlString = await wasm.exportIDS(doc);
|
||||
const xmlString = await wasm.exportIDS(doc as Record<string, unknown>) as string;
|
||||
|
||||
return xmlString;
|
||||
}
|
||||
|
||||
export async function exportDocument(docId) {
|
||||
export async function exportDocument(docId: string) {
|
||||
const doc = $state.snapshot(Module.documents[docId]);
|
||||
|
||||
// Validate
|
||||
@@ -162,37 +174,38 @@ export async function exportDocument(docId) {
|
||||
throw new Error("Please create at least one specification before exporting the document.");
|
||||
}
|
||||
|
||||
const xmlString = await wasm.exportIDS(doc);
|
||||
const xmlString = await wasm.exportIDS(doc as Record<string, unknown>) as string;
|
||||
|
||||
// Create and download file
|
||||
const blob = new Blob([xmlString], { type: 'application/xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${Module.documents[docId].info.title.replace(/[^a-zA-Z0-9]/g, '_')}.ids`;
|
||||
const title = Module.documents[docId].info.title || "untitled";
|
||||
a.download = `${title.replace(/[^a-zA-Z0-9]/g, '_')}.ids`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export async function createSpecification(docId) {
|
||||
const spec = await wasm.createSpecification();
|
||||
export async function createSpecification(docId: string) {
|
||||
const spec = await wasm.createSpecification() as Specification;
|
||||
|
||||
// Add specification to document
|
||||
Module.documents[docId].specifications.specification.push(spec);
|
||||
|
||||
// Set as active specification
|
||||
if (Module.activeDocument == docId) {
|
||||
if (Module.activeDocument === docId) {
|
||||
const state = Module.states[docId];
|
||||
state.activeSpecification = Module.documents[docId].specifications.specification.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSpecification(docId, specId) {
|
||||
export async function deleteSpecification(docId: string, specId: number) {
|
||||
Module.documents[docId].specifications.specification.splice(specId, 1);
|
||||
|
||||
if (Module.activeDocument == docId) {
|
||||
if (Module.activeDocument === docId) {
|
||||
const state = Module.states[docId];
|
||||
if (state.activeSpecification == specId) {
|
||||
if (state.activeSpecification === specId) {
|
||||
// We need to wait for the next tick here because of Svelte's internal shenanigans
|
||||
await tick();
|
||||
setDocumentState(docId, { activeSpecification: null });
|
||||
@@ -209,124 +222,148 @@ export async function deleteSpecification(docId, specId) {
|
||||
* clause: "applicability", "requirements"
|
||||
* facet: "entity", "attribute", "classification", "partOf", "property", "material"
|
||||
*/
|
||||
export async function createFacet(docId, specId, clause, facet) {
|
||||
let facetObj;
|
||||
if (facet == "entity") {
|
||||
facetObj = await wasm.createEntityFacet(clause, {});
|
||||
} else if (facet == "attribute") {
|
||||
facetObj = await wasm.createAttributeFacet(clause, {});
|
||||
} else if (facet == "classification") {
|
||||
facetObj = await wasm.createClassificationFacet(clause, {});
|
||||
} else if (facet == "partOf") {
|
||||
facetObj = await wasm.createPartOfFacet(clause, {});
|
||||
} else if (facet == "property") {
|
||||
facetObj = await wasm.createPropertyFacet(clause, {});
|
||||
} else if (facet == "material") {
|
||||
facetObj = await wasm.createMaterialFacet(clause, {});
|
||||
export async function createFacet(
|
||||
docId: string,
|
||||
specId: number,
|
||||
clause: "applicability" | "requirements",
|
||||
facet: "entity" | "attribute" | "classification" | "partOf" | "property" | "material"
|
||||
) {
|
||||
let facetObj: Facet | undefined;
|
||||
if (facet === "entity") {
|
||||
facetObj = await wasm.createEntityFacet(clause, {}) as Facet;
|
||||
} else if (facet === "attribute") {
|
||||
facetObj = await wasm.createAttributeFacet(clause, {}) as Facet;
|
||||
} else if (facet === "classification") {
|
||||
facetObj = await wasm.createClassificationFacet(clause, {}) as Facet;
|
||||
} else if (facet === "partOf") {
|
||||
facetObj = await wasm.createPartOfFacet(clause, {}) as Facet;
|
||||
} else if (facet === "property") {
|
||||
facetObj = await wasm.createPropertyFacet(clause, {}) as Facet;
|
||||
} else if (facet === "material") {
|
||||
facetObj = await wasm.createMaterialFacet(clause, {}) as Facet;
|
||||
}
|
||||
|
||||
if (!(facet in Module.documents[docId].specifications.specification[specId][clause])) {
|
||||
Module.documents[docId].specifications.specification[specId][clause][facet] = [];
|
||||
if (!facetObj) return;
|
||||
|
||||
const spec = Module.documents[docId].specifications.specification[specId];
|
||||
const clauseKey = clause as "applicability" | "requirements";
|
||||
if (!spec[clauseKey]) spec[clauseKey] = {};
|
||||
if (!(facet in (spec[clauseKey] as Record<string, unknown>))) {
|
||||
(spec[clauseKey] as Record<string, unknown>)[facet] = [];
|
||||
}
|
||||
|
||||
Module.documents[docId].specifications.specification[specId][clause][facet].push(facetObj);
|
||||
((spec[clauseKey] as Record<string, unknown>)[facet] as Facet[]).push(facetObj);
|
||||
}
|
||||
|
||||
export async function deleteFacet(docId, specId, clause, facet, facetId) {
|
||||
delete Module.documents[docId].specifications.specification[specId][clause][facet][facetId];
|
||||
export async function deleteFacet(
|
||||
docId: string,
|
||||
specId: number,
|
||||
clause: "applicability" | "requirements",
|
||||
facet: "entity" | "attribute" | "classification" | "partOf" | "property" | "material",
|
||||
facetId: number
|
||||
) {
|
||||
const spec = Module.documents[docId].specifications.specification[specId];
|
||||
const list = (spec[clause] as Record<string, unknown> | undefined)?.[facet] as Facet[] | undefined;
|
||||
if (!list) return;
|
||||
list.splice(facetId, 1);
|
||||
}
|
||||
|
||||
export function getSpecUsage(spec) {
|
||||
export function getSpecUsage(spec?: Specification | null): IdsCardinality {
|
||||
if (!spec?.applicability) return 'required';
|
||||
const minOccurs = spec.applicability["@minOccurs"];
|
||||
const maxOccurs = spec.applicability["@maxOccurs"];
|
||||
|
||||
if (minOccurs === 1 && maxOccurs === "unbounded") return 'required';
|
||||
if (minOccurs === 0 && maxOccurs === "unbounded") return 'optional';
|
||||
if (minOccurs === 0 && maxOccurs === 0) return 'prohibited';
|
||||
const minOccurs = spec.applicability["@minOccurs"] as number | undefined;
|
||||
const maxOccurs = spec.applicability["@maxOccurs"] as number | "unbounded" | undefined;
|
||||
|
||||
if (minOccurs !== 0) return 'required';
|
||||
if (minOccurs === 0 && maxOccurs !== 0) return 'optional';
|
||||
if (maxOccurs === 0) return 'prohibited';
|
||||
return 'required';
|
||||
};
|
||||
|
||||
// Converts facet to human-readable description
|
||||
export function stringifyFacet(clauseType, facet, facetType, spec) {
|
||||
export function stringifyFacet(
|
||||
clauseType: "applicability" | "requirements",
|
||||
facet: Facet,
|
||||
facetType: string,
|
||||
spec?: Specification | null
|
||||
) {
|
||||
if (!facet) return "";
|
||||
|
||||
const usage = getSpecUsage(spec);
|
||||
const descriptions = [];
|
||||
const descriptions: string[] = [];
|
||||
|
||||
// Entity facet
|
||||
if (facetType === "entity") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`All data where IFC class ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`All data where IFC class ${stringifyValue(facet.name as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be data where IFC class ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`Shall be data where IFC class ${stringifyValue(facet.name as FacetValue)}`);
|
||||
}
|
||||
|
||||
if (facet.predefinedType) {
|
||||
descriptions.push(`and type ${stringifyValue(facet.predefinedType)}`);
|
||||
descriptions.push(`and type ${stringifyValue(facet.predefinedType as FacetValue)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute facet
|
||||
else if (facetType === "attribute") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`All data where attribute ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`All data where attribute ${stringifyValue(facet.name as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be data where attribute ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`Shall be data where attribute ${stringifyValue(facet.name as FacetValue)}`);
|
||||
}
|
||||
descriptions.push(`and value ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`and value ${stringifyValue(facet.value as FacetValue)}`);
|
||||
}
|
||||
|
||||
// Property facet
|
||||
else if (facetType === "property") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`Elements where property ${stringifyValue(facet.baseName)}`);
|
||||
descriptions.push(`Elements where property ${stringifyValue(facet.baseName as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be elements where property ${stringifyValue(facet.baseName)}`);
|
||||
descriptions.push(`Shall be elements where property ${stringifyValue(facet.baseName as FacetValue)}`);
|
||||
}
|
||||
if (facet.value) {
|
||||
descriptions.push(`and value ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`and value ${stringifyValue(facet.value as FacetValue)}`);
|
||||
}
|
||||
descriptions.push(`and dataset ${stringifyValue(facet.propertySet)}`);
|
||||
descriptions.push(`and dataset ${stringifyValue(facet.propertySet as FacetValue)}`);
|
||||
}
|
||||
|
||||
// Classification facet
|
||||
else if (facetType === "classification") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`All data where classification system ${stringifyValue(facet.system)}`);
|
||||
descriptions.push(`All data where classification system ${stringifyValue(facet.system as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be data where classification system ${stringifyValue(facet.system)}`);
|
||||
descriptions.push(`Shall be data where classification system ${stringifyValue(facet.system as FacetValue)}`);
|
||||
}
|
||||
if (facet.value) {
|
||||
descriptions.push(`and classification ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`and classification ${stringifyValue(facet.value as FacetValue)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Material facet
|
||||
else if (facetType === "material") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`All data where material ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`All data where material ${stringifyValue(facet.value as FacetValue)}`);
|
||||
} else {
|
||||
descriptions.push(`Shall be data where material ${stringifyValue(facet.value)}`);
|
||||
descriptions.push(`Shall be data where material ${stringifyValue(facet.value as FacetValue)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// PartOf facet
|
||||
else if (facetType === "partOf") {
|
||||
if (clauseType === "applicability") {
|
||||
descriptions.push(`An element with an **${facet['@relation']}** relationship`);
|
||||
descriptions.push(`An element with an **${String(facet['@relation'] ?? "")}** relationship`);
|
||||
|
||||
if (facet.name) {
|
||||
descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name as FacetValue)}`);
|
||||
}
|
||||
} else {
|
||||
descriptions.push(`An element shall have an **${facet['@relation']}** relationship`);
|
||||
descriptions.push(`An element shall have an **${String(facet['@relation'] ?? "")}** relationship`);
|
||||
|
||||
if (facet.name) {
|
||||
descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name)}`);
|
||||
descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name as FacetValue)}`);
|
||||
}
|
||||
if (facet.predefinedType) {
|
||||
descriptions.push(`and predefined type ${stringifyValue(facet.predefinedType)}`);
|
||||
descriptions.push(`and predefined type ${stringifyValue(facet.predefinedType as FacetValue)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,20 +373,22 @@ export function stringifyFacet(clauseType, facet, facetType, spec) {
|
||||
// Post-process for prohibited and optional requirements
|
||||
let isProhibited = false;
|
||||
|
||||
if (usage == "prohibited") isProhibited = !isProhibited;
|
||||
if (clauseType == "requirements" && "@cardinality" in facet && facet["@cardinality"] == "prohibited") isProhibited = !isProhibited;
|
||||
if (usage === "prohibited") isProhibited = !isProhibited;
|
||||
if (clauseType === "requirements" && "@cardinality" in facet && facet["@cardinality"] === "prohibited") {
|
||||
isProhibited = !isProhibited;
|
||||
}
|
||||
|
||||
if (isProhibited)
|
||||
combined = combined.replace("Shall", "Shall not").replace("shall", "shall not");
|
||||
|
||||
if (clauseType == "requirements" && "@cardinality" in facet && facet["@cardinality"] == "optional")
|
||||
if (clauseType === "requirements" && "@cardinality" in facet && facet["@cardinality"] === "optional")
|
||||
combined = combined.replace("Shall", "May").replace("shall", "may");
|
||||
|
||||
return renderFacetString(combined);
|
||||
}
|
||||
|
||||
// Converts value objects to human-readable strings
|
||||
function stringifyValue(value) {
|
||||
function stringifyValue(value?: FacetValue) {
|
||||
if (!value) return "is provided";
|
||||
if (value.simpleValue) return `is **${value.simpleValue}**`;
|
||||
if (value.restriction) return stringifyRestriction(value.restriction);
|
||||
@@ -357,7 +396,7 @@ function stringifyValue(value) {
|
||||
}
|
||||
|
||||
// Converts restriction objects to human-readable strings
|
||||
function stringifyRestriction(restriction) {
|
||||
function stringifyRestriction(restriction: Restriction) {
|
||||
if (!restriction) return "";
|
||||
|
||||
// Handle enumeration
|
||||
@@ -394,7 +433,7 @@ function stringifyRestriction(restriction) {
|
||||
if (restriction.maxExclusive && restriction.maxExclusive.length > 0) {
|
||||
parts.push(`**< ${restriction.maxExclusive[0]['@value'] || ''}**`);
|
||||
}
|
||||
return parts.length > 0 ? "is in range " + parts.join(", ") : "has range restriction";
|
||||
return parts.length > 0 ? `is in range ${parts.join(", ")}` : "has range restriction";
|
||||
}
|
||||
|
||||
// Handle length range restrictions
|
||||
@@ -406,18 +445,18 @@ function stringifyRestriction(restriction) {
|
||||
if (restriction.maxLength && restriction.maxLength.length > 0) {
|
||||
parts.push(`**max length ${restriction.maxLength[0]['@value'] || ''}**`);
|
||||
}
|
||||
return parts.length > 0 ? "has " + parts.join(", ") : "has length range restriction";
|
||||
return parts.length > 0 ? `has ${parts.join(", ")}` : "has length range restriction";
|
||||
}
|
||||
|
||||
return "has complex restriction";
|
||||
}
|
||||
|
||||
function renderFacetString(text) {
|
||||
function renderFacetString(text: string): string {
|
||||
// Convert **text** to <strong>text</strong>
|
||||
text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
const withStrong = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// Convert `text` to <code>text</code>
|
||||
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
const withCode = withStrong.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
|
||||
return text;
|
||||
}
|
||||
return withCode;
|
||||
}
|
||||
+14
-8
@@ -4,7 +4,7 @@ import { toast } from "svelte-sonner";
|
||||
* Show an error toast notification
|
||||
* @param {string} message - The error message to display
|
||||
*/
|
||||
export function error(message) {
|
||||
export function error(message: string): void {
|
||||
toast.error(message);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function error(message) {
|
||||
* Show a success toast notification
|
||||
* @param {string} message - The success message to display
|
||||
*/
|
||||
export function success(message) {
|
||||
export function success(message: string): void {
|
||||
toast.success(message);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function success(message) {
|
||||
* Show an info toast notification
|
||||
* @param {string} message - The info message to display
|
||||
*/
|
||||
export function info(message) {
|
||||
export function info(message: string): void {
|
||||
toast.info(message);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function info(message) {
|
||||
* Show a warning toast notification
|
||||
* @param {string} message - The warning message to display
|
||||
*/
|
||||
export function warning(message) {
|
||||
export function warning(message: string): void {
|
||||
toast.warning(message);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function warning(message) {
|
||||
* @param {string} message - The loading message to display
|
||||
* @returns {string} - Toast ID for dismissing later
|
||||
*/
|
||||
export function loading(message) {
|
||||
export function loading(message: string): string | number {
|
||||
return toast.loading(message);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export function loading(message) {
|
||||
* Dismiss a specific toast
|
||||
* @param {string} toastId - The toast ID to dismiss
|
||||
*/
|
||||
export function dismiss(toastId) {
|
||||
export function dismiss(toastId: string | number): void {
|
||||
toast.dismiss(toastId);
|
||||
}
|
||||
|
||||
@@ -57,10 +57,16 @@ export function dismiss(toastId) {
|
||||
* @param {string} messages.success - Success message
|
||||
* @param {string} messages.error - Error message
|
||||
*/
|
||||
export function promise(promiseToTrack, messages) {
|
||||
type PromiseToastMessages = {
|
||||
loading: string;
|
||||
success: string;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export function promise<T>(promiseToTrack: Promise<T>, messages: PromiseToastMessages) {
|
||||
return toast.promise(promiseToTrack, {
|
||||
loading: messages.loading,
|
||||
success: messages.success,
|
||||
error: messages.error,
|
||||
});
|
||||
}
|
||||
}
|
||||
+53
-42
@@ -5,6 +5,7 @@
|
||||
|
||||
import hyperid from "hyperid";
|
||||
import EventEmitter from "eventemitter3";
|
||||
import type { WorkerResponse } from "$src/types/wasm";
|
||||
|
||||
// Message types
|
||||
export const MessageType = {
|
||||
@@ -25,52 +26,57 @@ export const MessageType = {
|
||||
|
||||
// WASM module disposed
|
||||
DISPOSED: 'disposed'
|
||||
} as const;
|
||||
|
||||
type PendingMessage = {
|
||||
resolve: (value?: unknown) => void;
|
||||
reject: (reason?: unknown) => void;
|
||||
};
|
||||
|
||||
type WasmReadyState = boolean | Promise<boolean>;
|
||||
|
||||
class WASMModule extends EventEmitter {
|
||||
id = hyperid();
|
||||
ready = false;
|
||||
worker = null;
|
||||
pendingMessages = new Map();
|
||||
ready: WasmReadyState = false;
|
||||
worker: Worker | null = null;
|
||||
pendingMessages = new Map<string, PendingMessage>();
|
||||
|
||||
async init() {
|
||||
if (this.ready === true) return;
|
||||
else if (this.ready instanceof Promise) return this.ready;
|
||||
if (this.ready instanceof Promise) return this.ready;
|
||||
|
||||
this.worker = new Worker(new URL('./worker/worker.js', import.meta.url), {type: 'module'});
|
||||
this.worker = new Worker(new URL('./worker/worker.ts', import.meta.url), {type: 'module'});
|
||||
|
||||
this.worker.onmessage = (event) => {
|
||||
this.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {
|
||||
this._handleWorkerMessage(event.data);
|
||||
};
|
||||
|
||||
this.worker.onerror = (error) => {
|
||||
this.worker.onerror = (error: ErrorEvent) => {
|
||||
console.error('[WASM] Web worker error:', error);
|
||||
this._rejectPendingMessages(error);
|
||||
};
|
||||
|
||||
this.ready = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
await this._sendMessage(MessageType.INIT);
|
||||
resolve(true);
|
||||
} catch (error) {
|
||||
this.ready = this._sendMessage(MessageType.INIT)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
console.error('[WASM] Failed to initialize:', error);
|
||||
this.ready = false;
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
|
||||
return this.ready;
|
||||
}
|
||||
|
||||
async _sendMessage(type, payload = {}) {
|
||||
if (!this.worker) throw new Error('Worker not initialized');
|
||||
async _sendMessage(type: string, payload: Record<string, unknown> = {}): Promise<unknown> {
|
||||
const worker = this.worker;
|
||||
if (!worker) throw new Error('Worker not initialized');
|
||||
|
||||
const id = this.id();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pendingMessages.set(id, { resolve, reject });
|
||||
|
||||
this.worker.postMessage({
|
||||
worker.postMessage({
|
||||
type,
|
||||
payload,
|
||||
id
|
||||
@@ -78,7 +84,7 @@ class WASMModule extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
_handleWorkerMessage({ type, payload, id }) {
|
||||
_handleWorkerMessage({ type, payload, id }: WorkerResponse) {
|
||||
const pendingMessage = this.pendingMessages.get(id);
|
||||
|
||||
if (!pendingMessage) {
|
||||
@@ -97,23 +103,28 @@ class WASMModule extends EventEmitter {
|
||||
case MessageType.API_RESPONSE:
|
||||
resolve(payload);
|
||||
break;
|
||||
case MessageType.ERROR:
|
||||
reject(new Error(payload.message));
|
||||
case MessageType.ERROR: {
|
||||
const message =
|
||||
payload && typeof payload === "object" && "message" in payload
|
||||
? String(payload.message)
|
||||
: "Unknown worker error";
|
||||
reject(new Error(message));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn('[WASM] Unknown message type:', type);
|
||||
reject(new Error(`Unknown message type: ${type}`));
|
||||
}
|
||||
}
|
||||
|
||||
_rejectPendingMessages(error) {
|
||||
_rejectPendingMessages(error: unknown) {
|
||||
for (const { reject } of this.pendingMessages.values()) {
|
||||
reject(error);
|
||||
}
|
||||
this.pendingMessages.clear();
|
||||
}
|
||||
|
||||
async _apiCall(method, ...args) {
|
||||
async _apiCall(method: string, ...args: unknown[]) {
|
||||
if (!this.ready) await this.init();
|
||||
|
||||
const result = await this._sendMessage(MessageType.API_CALL, { method, args });
|
||||
@@ -123,35 +134,35 @@ class WASMModule extends EventEmitter {
|
||||
/**
|
||||
* Get all entity classes in a given IFC schema
|
||||
*/
|
||||
async getAllEntityClasses(schema) {
|
||||
async getAllEntityClasses(schema: string) {
|
||||
return this._apiCall('getAllEntityClasses', schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data types in a given IFC schema
|
||||
*/
|
||||
async getAllDataTypes(schema) {
|
||||
async getAllDataTypes(schema: string) {
|
||||
return this._apiCall('getAllDataTypes', schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get predefined types for a given IFC entity
|
||||
*/
|
||||
async getPredefinedTypes(schema, entity) {
|
||||
async getPredefinedTypes(schema: string, entity: string) {
|
||||
return this._apiCall('getPredefinedTypes', schema, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all attributes for a given IFC entity
|
||||
*/
|
||||
async getEntityAttributes(schema, entity) {
|
||||
async getEntityAttributes(schema: string, entity: string) {
|
||||
return this._apiCall('getEntityAttributes', schema, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get applicable property sets for a given IFC entity
|
||||
*/
|
||||
async getApplicablePsets(schema, entity, predefinedType = '') {
|
||||
async getApplicablePsets(schema: string, entity: string, predefinedType = '') {
|
||||
return this._apiCall('getApplicablePsets', schema, entity, predefinedType);
|
||||
}
|
||||
|
||||
@@ -172,21 +183,21 @@ class WASMModule extends EventEmitter {
|
||||
/**
|
||||
* Load an IFC file. Returns a unique ID for the loaded file.
|
||||
*/
|
||||
async loadIfc(ifcData) {
|
||||
async loadIfc(ifcData: number[] | Uint8Array | ArrayBuffer) {
|
||||
return this._apiCall('loadIfc', ifcData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload an IFC file
|
||||
*/
|
||||
async unloadIfc(ifcId) {
|
||||
async unloadIfc(ifcId: string) {
|
||||
return this._apiCall('unloadIfc', ifcId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit a loaded IFC file against IDS specifications
|
||||
*/
|
||||
async auditIfc(ifcId, idsData) {
|
||||
async auditIfc(ifcId: string, idsData: ArrayBuffer | Uint8Array | number[]) {
|
||||
const idsBytes = idsData instanceof ArrayBuffer ? new Uint8Array(idsData) : idsData;
|
||||
|
||||
return this._apiCall('auditIfc', ifcId, Array.from(idsBytes));
|
||||
@@ -204,70 +215,70 @@ class WASMModule extends EventEmitter {
|
||||
/**
|
||||
* Open an existing IDS from XML string
|
||||
*/
|
||||
async openIDS(idsXml, validate = false) {
|
||||
async openIDS(idsXml: string, validate = false) {
|
||||
return this._apiCall('openIDS', idsXml, validate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a specification
|
||||
*/
|
||||
async createSpecification(options = {}) {
|
||||
async createSpecification(options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createSpecification', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an entity facet
|
||||
*/
|
||||
async createEntityFacet(clause, options = {}) {
|
||||
async createEntityFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createEntityFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an attribute facet
|
||||
*/
|
||||
async createAttributeFacet(clause, options = {}) {
|
||||
async createAttributeFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createAttributeFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a property facet
|
||||
*/
|
||||
async createPropertyFacet(clause, options = {}) {
|
||||
async createPropertyFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createPropertyFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a material facet
|
||||
*/
|
||||
async createMaterialFacet(clause, options = {}) {
|
||||
async createMaterialFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createMaterialFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a classification facet
|
||||
*/
|
||||
async createClassificationFacet(clause, options = {}) {
|
||||
async createClassificationFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createClassificationFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a part-of facet
|
||||
*/
|
||||
async createPartOfFacet(clause, options = {}) {
|
||||
async createPartOfFacet(clause: string, options: Record<string, unknown> = {}) {
|
||||
return this._apiCall('createPartOfFacet', clause, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an IDS object
|
||||
*/
|
||||
async validateIDS(idsObj) {
|
||||
async validateIDS(idsObj: Record<string, unknown>) {
|
||||
return await this._apiCall('validateIDS', idsObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export IDS instance to XML string
|
||||
*/
|
||||
async exportIDS(idsObj) {
|
||||
async exportIDS(idsObj: Record<string, unknown>) {
|
||||
return this._apiCall('exportIDS', idsObj);
|
||||
}
|
||||
|
||||
@@ -319,4 +330,4 @@ export const {
|
||||
dispose
|
||||
} = wasm;
|
||||
|
||||
export default wasm;
|
||||
export default wasm;
|
||||
+18
-17
@@ -1,12 +1,13 @@
|
||||
import config from '../../../config.json';
|
||||
import hyperid from 'hyperid';
|
||||
import config from '../../../config.json';
|
||||
import type { AuditReportData } from "$src/types/report";
|
||||
|
||||
let pyodide = null;
|
||||
let id = hyperid();
|
||||
let pyodide: any = null;
|
||||
const id = hyperid();
|
||||
|
||||
let LoadedIFC = new Map();
|
||||
const LoadedIFC = new Map<string, unknown>();
|
||||
|
||||
export async function init(pdide) {
|
||||
export async function init(pdide: any) {
|
||||
pyodide = pdide;
|
||||
|
||||
// Load Python API bindings
|
||||
@@ -18,7 +19,7 @@ export async function init(pdide) {
|
||||
`);
|
||||
}
|
||||
|
||||
export async function getPredefinedTypes(schema, entity) {
|
||||
export async function getPredefinedTypes(schema: string, entity: string) {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_predefined_types_for_entity
|
||||
predef_types = get_predefined_types_for_entity("${schema}", "${entity}")
|
||||
@@ -27,7 +28,7 @@ export async function getPredefinedTypes(schema, entity) {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function getAllEntityClasses(schema) {
|
||||
export async function getAllEntityClasses(schema: string) {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_all_entity_classes
|
||||
entities = get_all_entity_classes("${schema}")
|
||||
@@ -36,7 +37,7 @@ export async function getAllEntityClasses(schema) {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function getAllDataTypes(schema) {
|
||||
export async function getAllDataTypes(schema: string) {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_all_data_types
|
||||
data_types = get_all_data_types("${schema}")
|
||||
@@ -45,7 +46,7 @@ export async function getAllDataTypes(schema) {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function getEntityAttributes(schema, entity) {
|
||||
export async function getEntityAttributes(schema: string, entity: string) {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_entity_attributes
|
||||
attrs = get_entity_attributes("${schema}", "${entity}")
|
||||
@@ -54,7 +55,7 @@ export async function getEntityAttributes(schema, entity) {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function getApplicablePsets(schema, entity, predefinedType = '') {
|
||||
export async function getApplicablePsets(schema: string, entity: string, predefinedType = '') {
|
||||
const result = await pyodide.runPythonAsync(`
|
||||
from api import get_applicable_psets
|
||||
psets = get_applicable_psets("${schema}", "${entity}", "${predefinedType}")
|
||||
@@ -81,7 +82,7 @@ export async function getStandardClassificationSystems() {
|
||||
return result.toJs({ dict_converter: Object.fromEntries });
|
||||
}
|
||||
|
||||
export async function loadIfc(ifcData) {
|
||||
export async function loadIfc(ifcData: number[] | Uint8Array | ArrayBuffer) {
|
||||
const ifc_id = id();
|
||||
const path = `/tmp/${encodeURIComponent(ifc_id)}.ifc`;
|
||||
|
||||
@@ -97,14 +98,14 @@ export async function loadIfc(ifcData) {
|
||||
return ifc_id;
|
||||
}
|
||||
|
||||
export async function unloadIfc(ifcId) {
|
||||
export async function unloadIfc(ifcId: string) {
|
||||
const path = `/tmp/${encodeURIComponent(ifcId)}.ifc`;
|
||||
|
||||
pyodide.FS.unlink(path);
|
||||
LoadedIFC.delete(ifcId);
|
||||
}
|
||||
|
||||
export async function auditIfc(ifcId, idsData) {
|
||||
export async function auditIfc(ifcId: string, idsData: number[] | Uint8Array | ArrayBuffer) {
|
||||
const reporter = pyodide.pyimport("ifctester.reporter");
|
||||
const api = pyodide.pyimport("api");
|
||||
|
||||
@@ -116,16 +117,16 @@ export async function auditIfc(ifcId, idsData) {
|
||||
specs.validate(ifc);
|
||||
|
||||
// Create report in both HTML and JSON formats
|
||||
let jsonReporter = reporter.Json(specs);
|
||||
const jsonReporter = reporter.Json(specs);
|
||||
jsonReporter.report();
|
||||
const jsonReport = jsonReporter.to_string();
|
||||
|
||||
let htmlReporter = reporter.Html(specs);
|
||||
const htmlReporter = reporter.Html(specs);
|
||||
htmlReporter.report();
|
||||
const htmlReport = htmlReporter.to_string();
|
||||
|
||||
return {
|
||||
json: JSON.parse(jsonReport),
|
||||
json: JSON.parse(jsonReport) as AuditReportData,
|
||||
html: htmlReport
|
||||
};
|
||||
}
|
||||
@@ -142,4 +143,4 @@ export const API = {
|
||||
"loadIfc": loadIfc,
|
||||
"unloadIfc": unloadIfc,
|
||||
"auditIfc": auditIfc
|
||||
};
|
||||
};
|
||||
+98
-18
@@ -2,13 +2,19 @@
|
||||
* IDS module
|
||||
*/
|
||||
|
||||
let pyodide = null;
|
||||
let pyodide: any = null;
|
||||
|
||||
// IDS Python classes
|
||||
let Ids, Specification;
|
||||
let Entity, Attribute, Property, Material, Classification, PartOf;
|
||||
let Ids: any;
|
||||
let Specification: any;
|
||||
let Entity: any;
|
||||
let Attribute: any;
|
||||
let Property: any;
|
||||
let Material: any;
|
||||
let Classification: any;
|
||||
let PartOf: any;
|
||||
|
||||
export async function init(pdide) {
|
||||
export async function init(pdide: any) {
|
||||
pyodide = pdide;
|
||||
|
||||
await pyodide.loadPackagesFromImports(`
|
||||
@@ -29,24 +35,24 @@ export async function init(pdide) {
|
||||
PartOf = pyodide.pyimport("ifctester.facet").PartOf;
|
||||
}
|
||||
|
||||
function _idsToInstance(idsObj) {
|
||||
function _idsToInstance(idsObj: Record<string, unknown>) {
|
||||
const ids_raw = Ids();
|
||||
return ids_raw.parse(pyodide.toPy(idsObj))
|
||||
}
|
||||
|
||||
export function createIDS() {
|
||||
export function createIDS(): Record<string, unknown> {
|
||||
const ids_raw = Ids()
|
||||
return ids_raw.asdict().toJs({dict_converter: Object.fromEntries});
|
||||
}
|
||||
|
||||
export function openIDS(ids_xml, validate = false) {
|
||||
export function openIDS(ids_xml: string, validate = false): Record<string, unknown> {
|
||||
const ids_from_xml_string = pyodide.pyimport("api").ids_from_xml_string;
|
||||
const ids_raw = ids_from_xml_string(ids_xml, validate);
|
||||
|
||||
return ids_raw.asdict().toJs({dict_converter: Object.fromEntries});
|
||||
}
|
||||
|
||||
export function validateIDS(idsObj) {
|
||||
export function validateIDS(idsObj: Record<string, unknown>): boolean {
|
||||
const ids_raw = _idsToInstance(idsObj)
|
||||
const tempFilename = `temp_${Date.now()}.xml`;
|
||||
const isValid = ids_raw.to_xml(tempFilename); // to_xml validates the XML as well, as far as I understand
|
||||
@@ -60,12 +66,26 @@ export function validateIDS(idsObj) {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export function exportIDS(idsObj) {
|
||||
export function exportIDS(idsObj: Record<string, unknown>): string {
|
||||
const ids_raw = _idsToInstance(idsObj)
|
||||
return ids_raw.to_string();
|
||||
}
|
||||
|
||||
export function createSpecification({name = "Unnamed", ifcVersion = ["IFC2X3", "IFC4"], identifier = null, description = null, instructions = null, usage = "required"}) {
|
||||
export function createSpecification({
|
||||
name = "Unnamed",
|
||||
ifcVersion = ["IFC2X3", "IFC4"],
|
||||
identifier = null,
|
||||
description = null,
|
||||
instructions = null,
|
||||
usage = "required"
|
||||
}: {
|
||||
name?: string;
|
||||
ifcVersion?: string[];
|
||||
identifier?: string | null;
|
||||
description?: string | null;
|
||||
instructions?: string | null;
|
||||
usage?: string;
|
||||
} = {}): Record<string, unknown> {
|
||||
const spec = Specification.callKwargs({
|
||||
name: name,
|
||||
ifcVersion: ifcVersion,
|
||||
@@ -79,7 +99,14 @@ export function createSpecification({name = "Unnamed", ifcVersion = ["IFC2X3", "
|
||||
}
|
||||
|
||||
// @instructions
|
||||
export function createEntityFacet(clause, {name = "IFCWALL", predefinedType = null, instructions = null}) {
|
||||
export function createEntityFacet(
|
||||
clause: string,
|
||||
{name = "IFCWALL", predefinedType = null, instructions = null}: {
|
||||
name?: string;
|
||||
predefinedType?: string | null;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const entity = Entity.callKwargs({
|
||||
name: name,
|
||||
predefinedType: predefinedType,
|
||||
@@ -89,7 +116,15 @@ export function createEntityFacet(clause, {name = "IFCWALL", predefinedType = nu
|
||||
}
|
||||
|
||||
// @cardinality, @instructions
|
||||
export function createAttributeFacet(clause, {name = "Name", value = null, cardinality = "required", instructions = null}) {
|
||||
export function createAttributeFacet(
|
||||
clause: string,
|
||||
{name = "Name", value = null, cardinality = "required", instructions = null}: {
|
||||
name?: string;
|
||||
value?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const attribute = Attribute.callKwargs({
|
||||
name: name,
|
||||
value: value,
|
||||
@@ -100,7 +135,16 @@ export function createAttributeFacet(clause, {name = "Name", value = null, cardi
|
||||
}
|
||||
|
||||
// @uri, @cardinality, @instructions
|
||||
export function createClassificationFacet(clause, {value = null, system = null, uri = null, cardinality = "required", instructions = null}) {
|
||||
export function createClassificationFacet(
|
||||
clause: string,
|
||||
{value = null, system = null, uri = null, cardinality = "required", instructions = null}: {
|
||||
value?: string | null;
|
||||
system?: string | null;
|
||||
uri?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const classification = Classification.callKwargs({
|
||||
value: value,
|
||||
system: system,
|
||||
@@ -112,7 +156,16 @@ export function createClassificationFacet(clause, {value = null, system = null,
|
||||
}
|
||||
|
||||
// @relation, @cardinality, @instructions
|
||||
export function createPartOfFacet(clause, {name = "IFCWALL", predefinedType = null, relation = null, cardinality = "required", instructions = null}) {
|
||||
export function createPartOfFacet(
|
||||
clause: string,
|
||||
{name = "IFCWALL", predefinedType = null, relation = null, cardinality = "required", instructions = null}: {
|
||||
name?: string;
|
||||
predefinedType?: string | null;
|
||||
relation?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const part_of = PartOf.callKwargs({
|
||||
name: name,
|
||||
predefinedType: predefinedType,
|
||||
@@ -124,7 +177,26 @@ export function createPartOfFacet(clause, {name = "IFCWALL", predefinedType = nu
|
||||
}
|
||||
|
||||
// @dataType, @uri, @cardinality, @instructions
|
||||
export function createPropertyFacet(clause, {propertySet = "Property_Set", baseName = "propertyName", value = null, dataType = null, uri = null, cardinality = "required", instructions = null}) {
|
||||
export function createPropertyFacet(
|
||||
clause: string,
|
||||
{
|
||||
propertySet = "Property_Set",
|
||||
baseName = "propertyName",
|
||||
value = null,
|
||||
dataType = null,
|
||||
uri = null,
|
||||
cardinality = "required",
|
||||
instructions = null
|
||||
}: {
|
||||
propertySet?: string;
|
||||
baseName?: string;
|
||||
value?: string | null;
|
||||
dataType?: string | null;
|
||||
uri?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const property = Property.callKwargs({
|
||||
propertySet: propertySet,
|
||||
baseName: baseName,
|
||||
@@ -138,7 +210,15 @@ export function createPropertyFacet(clause, {propertySet = "Property_Set", baseN
|
||||
}
|
||||
|
||||
// @uri, @cardinality, @instructions
|
||||
export function createMaterialFacet(clause, {value = null, uri = null, cardinality = "required", instructions = null}) {
|
||||
export function createMaterialFacet(
|
||||
clause: string,
|
||||
{value = null, uri = null, cardinality = "required", instructions = null}: {
|
||||
value?: string | null;
|
||||
uri?: string | null;
|
||||
cardinality?: string;
|
||||
instructions?: string | null;
|
||||
} = {}
|
||||
): Record<string, unknown> {
|
||||
const material = Material.callKwargs({
|
||||
value: value,
|
||||
uri: uri,
|
||||
@@ -149,7 +229,7 @@ export function createMaterialFacet(clause, {value = null, uri = null, cardinali
|
||||
}
|
||||
|
||||
// Helper function to convert date to ISO format string
|
||||
export function formatDate(date) {
|
||||
export function formatDate(date?: string | number | Date | null): string | null {
|
||||
if (!date) return null;
|
||||
const d = new Date(date);
|
||||
return d.toISOString().split('T')[0];
|
||||
@@ -168,4 +248,4 @@ export const API = {
|
||||
"createPartOfFacet": createPartOfFacet,
|
||||
"createPropertyFacet": createPropertyFacet,
|
||||
"createMaterialFacet": createMaterialFacet,
|
||||
};
|
||||
};
|
||||
+28
-15
@@ -4,13 +4,14 @@
|
||||
|
||||
import { MessageType } from '../index';
|
||||
import config from '../../../config.json';
|
||||
import * as IDS from './ids.js';
|
||||
import * as IDS from './ids';
|
||||
import * as API from './api';
|
||||
import type { ApiCallPayload, WorkerRequest } from "$src/types/wasm";
|
||||
|
||||
let pyodide = null;
|
||||
let pyodide: any = null;
|
||||
let ready = false;
|
||||
|
||||
self.addEventListener('message', async (event) => {
|
||||
self.addEventListener('message', async (event: MessageEvent<WorkerRequest>) => {
|
||||
console.log("[worker] Received message:", event.data);
|
||||
const { type, payload, id } = event.data;
|
||||
|
||||
@@ -25,27 +26,33 @@ self.addEventListener('message', async (event) => {
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageType.API_CALL:
|
||||
case MessageType.API_CALL: {
|
||||
if (!ready) {
|
||||
throw new Error('[worker] Pyodide not initialized');
|
||||
}
|
||||
const result = await handleApiCall(payload);
|
||||
if (!payload) {
|
||||
throw new Error('[worker] Missing payload for API call');
|
||||
}
|
||||
const result = await handleApiCall(payload as ApiCallPayload);
|
||||
self.postMessage({
|
||||
type: MessageType.API_RESPONSE,
|
||||
payload: result,
|
||||
id
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`[worker] Unknown message type: ${type}`);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const stack = error instanceof Error ? error.stack : undefined;
|
||||
self.postMessage({
|
||||
type: MessageType.ERROR,
|
||||
payload: {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
message,
|
||||
stack
|
||||
},
|
||||
id
|
||||
});
|
||||
@@ -76,7 +83,13 @@ async function initEnvironment() {
|
||||
await pyodide.loadPackage("shapely");
|
||||
|
||||
// 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
|
||||
await API.init(pyodide);
|
||||
@@ -93,17 +106,17 @@ async function cleanupEnvironment() {
|
||||
console.log("[worker] Closed environment");
|
||||
}
|
||||
|
||||
async function handleApiCall({ method, args = [] }) {
|
||||
async function handleApiCall({ method, args = [] }: ApiCallPayload) {
|
||||
if (method === 'internal.cleanup') {
|
||||
await cleanupEnvironment();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (method in API.API) {
|
||||
return await API.API[method](...args);
|
||||
} else if (method in IDS.API) {
|
||||
return await IDS.API[method](...args);
|
||||
} else {
|
||||
throw new Error(`[worker] Unknown API method: ${method}`);
|
||||
return await (API.API as Record<string, (...params: unknown[]) => unknown>)[method](...args);
|
||||
}
|
||||
}
|
||||
if (method in IDS.API) {
|
||||
return await (IDS.API as Record<string, (...params: unknown[]) => unknown>)[method](...args);
|
||||
}
|
||||
throw new Error(`[worker] Unknown API method: ${method}`);
|
||||
}
|
||||
@@ -1,37 +1,56 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import FacetEditor from './FacetEditor.svelte';
|
||||
import CreateFacetDropdown from "$src/components/CreateFacetDropdown.svelte";
|
||||
import type { DocumentState, Facet, IdsDocument, Specification } from "$src/types/ids";
|
||||
|
||||
let { activeTab } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let activeSpecification = $derived(
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null
|
||||
);
|
||||
|
||||
async function addFacet (facetType) {
|
||||
if (!activeSpecification) return;
|
||||
async function addFacet(facetType: FacetType) {
|
||||
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||
|
||||
await IDS.createFacet(
|
||||
IDS.Module.activeDocument,
|
||||
documentState.activeSpecification,
|
||||
documentState.activeSpecification ?? 0,
|
||||
"applicability",
|
||||
facetType
|
||||
);
|
||||
}
|
||||
|
||||
async function removeFacet(facetType, facetIndex) {
|
||||
if (!activeSpecification) return;
|
||||
async function removeFacet(facetType: FacetType, facetIndex: number) {
|
||||
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||
|
||||
await IDS.deleteFacet(
|
||||
IDS.Module.activeDocument,
|
||||
documentState.activeSpecification,
|
||||
documentState.activeSpecification ?? 0,
|
||||
"applicability",
|
||||
facetType,
|
||||
facetIndex
|
||||
);
|
||||
}
|
||||
|
||||
let applicabilityEntries = $derived(
|
||||
activeSpecification?.applicability
|
||||
? (Object.entries(activeSpecification.applicability).filter(
|
||||
([facetType, facets]) =>
|
||||
facetType !== "@minOccurs" &&
|
||||
facetType !== "@maxOccurs" &&
|
||||
Array.isArray(facets)
|
||||
) as Array<[FacetType, Facet[]]>)
|
||||
: []
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="restrictions-panel">
|
||||
@@ -40,22 +59,19 @@
|
||||
<CreateFacetDropdown {addFacet} />
|
||||
</div>
|
||||
<div class="restrictions-list">
|
||||
{#if activeSpecification?.applicability}
|
||||
{#each Object.entries(activeSpecification.applicability) as [facetType, facets]}
|
||||
{#if facetType !== "@minOccurs" && facetType !== "@maxOccurs"}
|
||||
{#each facets as facet, index}
|
||||
<FacetEditor
|
||||
bind:facet={facets[index]}
|
||||
{facetType}
|
||||
specification={activeSpecification}
|
||||
activeTab="applicability"
|
||||
{removeFacet}
|
||||
{index}
|
||||
key={`${activeDocument}-${documentState?.activeSpecification}-applicability-${facetType}-${index}`}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if activeSpecification && applicabilityEntries.length > 0}
|
||||
{#each applicabilityEntries as [facetType, facets]}
|
||||
{#each facets as facet, index}
|
||||
<FacetEditor
|
||||
bind:facet={facets[index]}
|
||||
{facetType}
|
||||
specification={activeSpecification}
|
||||
activeTab="applicability"
|
||||
{removeFacet}
|
||||
{index}
|
||||
/>
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,40 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import RestrictionEditor from './RestrictionEditor.svelte';
|
||||
import {stringifyFacet} from "$src/modules/api/ids.svelte.js";
|
||||
import {stringifyFacet} from "$src/modules/api/ids.svelte";
|
||||
import type { Facet, Specification } from "$src/types/ids";
|
||||
|
||||
/**
|
||||
* Applicability facets wont have: "@uri", "@instructions", "@cardinality"
|
||||
* @ in name --> simple string value
|
||||
* else --> can be simpleValue, Restriction or list of Restrictions
|
||||
*/
|
||||
let { facet = $bindable(), facetType, activeTab, removeFacet, index, specification } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
const getSpecialProp = (prop) => {
|
||||
return facet[prop] ?? "";
|
||||
let {
|
||||
facet = $bindable<Facet>({}),
|
||||
facetType,
|
||||
activeTab,
|
||||
removeFacet,
|
||||
index,
|
||||
specification
|
||||
}: {
|
||||
facet: Facet;
|
||||
facetType: FacetType;
|
||||
activeTab: "applicability" | "requirements";
|
||||
removeFacet: (facetType: FacetType, facetIndex: number) => void | Promise<void>;
|
||||
index: number;
|
||||
specification: Specification;
|
||||
} = $props();
|
||||
|
||||
const getSpecialProp = (prop: string) => {
|
||||
const value = (facet as Record<string, unknown>)[prop];
|
||||
return typeof value === "string" ? value : "";
|
||||
};
|
||||
const setSpecialProp = (prop, value) => {
|
||||
facet[prop] = value;
|
||||
const setSpecialProp = (prop: string, value: string) => {
|
||||
(facet as Record<string, unknown>)[prop] = value;
|
||||
};
|
||||
|
||||
let baseId = $derived(`facet-${facetType}-${index}`);
|
||||
</script>
|
||||
|
||||
<div class="restriction-item">
|
||||
@@ -48,8 +68,8 @@
|
||||
<RestrictionEditor bind:facet={facet} fieldName="name" label="Entity Name" placeholder="e.g., IfcSpace" autocomplete="entityName" />
|
||||
<RestrictionEditor bind:facet={facet} fieldName="predefinedType" label="Predefined Type" placeholder="e.g., SOLIDWALL" autocomplete="predefinedType" />
|
||||
<div class="form-group">
|
||||
<label>Relation</label>
|
||||
<select class="form-input" bind:value={() => getSpecialProp("@relation"), (v) => setSpecialProp("@relation", v)}>
|
||||
<label for={`${baseId}-relation`}>Relation</label>
|
||||
<select class="form-input" id={`${baseId}-relation`} bind:value={() => getSpecialProp("@relation"), (v) => setSpecialProp("@relation", v)}>
|
||||
<option value="">Select relation...</option>
|
||||
<option value="IFCRELAGGREGATES">IFCRELAGGREGATES</option>
|
||||
<option value="IFCRELASSIGNSTOGROUP">IFCRELASSIGNSTOGROUP</option>
|
||||
@@ -62,8 +82,8 @@
|
||||
{#if activeTab === 'requirements'}
|
||||
{#if facetType !== 'entity'}
|
||||
<div class="form-group">
|
||||
<label>Cardinality</label>
|
||||
<select class="form-input" bind:value={() => getSpecialProp("@cardinality"), (v) => setSpecialProp("@cardinality", v)}>
|
||||
<label for={`${baseId}-cardinality`}>Cardinality</label>
|
||||
<select class="form-input" id={`${baseId}-cardinality`} bind:value={() => getSpecialProp("@cardinality"), (v) => setSpecialProp("@cardinality", v)}>
|
||||
<option value="required">Required</option>
|
||||
<option value="optional">Optional</option>
|
||||
<option value="prohibited">Prohibited</option>
|
||||
@@ -71,9 +91,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="form-group full-width">
|
||||
<label>Instructions</label>
|
||||
<textarea class="form-input" bind:value={() => getSpecialProp("@instructions"), (v) => setSpecialProp("@instructions", v)} placeholder="Optional instructions for IFC authors" rows="2"></textarea>
|
||||
<label for={`${baseId}-instructions`}>Instructions</label>
|
||||
<textarea class="form-input" id={`${baseId}-instructions`} bind:value={() => getSpecialProp("@instructions"), (v) => setSpecialProp("@instructions", v)} placeholder="Optional instructions for IFC authors" rows="2"></textarea>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import type { IdsDocument, IdsInfo } from "$src/types/ids";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
|
||||
const getProp = (prop) => {
|
||||
const getProp = (prop: keyof IdsInfo) => {
|
||||
return activeDocument?.info[prop] ?? "";
|
||||
};
|
||||
|
||||
const setProp = (prop, value) => {
|
||||
const setProp = (prop: keyof IdsInfo, value: string) => {
|
||||
if (!activeDocument) return;
|
||||
activeDocument.info[prop] = value;
|
||||
};
|
||||
</script>
|
||||
@@ -18,36 +22,36 @@
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>Title</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("title"), (v) => setProp("title", v)} placeholder="Enter IDS title">
|
||||
<label for="ids-title">Title</label>
|
||||
<input class="form-input" id="ids-title" type="text" bind:value={() => getProp("title"), (v) => setProp("title", v)} placeholder="Enter IDS title">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Author Email</label>
|
||||
<input class="form-input" type="email" bind:value={() => getProp("author"), (v) => setProp("author", v)} placeholder="Enter author">
|
||||
<label for="ids-author">Author Email</label>
|
||||
<input class="form-input" id="ids-author" type="email" bind:value={() => getProp("author"), (v) => setProp("author", v)} placeholder="Enter author">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Version</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("version"), (v) => setProp("version", v)} placeholder="Enter version">
|
||||
<label for="ids-version">Version</label>
|
||||
<input class="form-input" id="ids-version" type="text" bind:value={() => getProp("version"), (v) => setProp("version", v)} placeholder="Enter version">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Date</label>
|
||||
<input class="form-input" type="date" bind:value={() => getProp("date"), (v) => setProp("date", v)}>
|
||||
<label for="ids-date">Date</label>
|
||||
<input class="form-input" id="ids-date" type="date" bind:value={() => getProp("date"), (v) => setProp("date", v)}>
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label>Description</label>
|
||||
<textarea class="form-input" bind:value={() => getProp("description"), (v) => setProp("description", v)} placeholder="Enter description" rows="3"></textarea>
|
||||
<label for="ids-description">Description</label>
|
||||
<textarea class="form-input" id="ids-description" bind:value={() => getProp("description"), (v) => setProp("description", v)} placeholder="Enter description" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Purpose</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("purpose"), (v) => setProp("purpose", v)} placeholder="Enter purpose">
|
||||
<label for="ids-purpose">Purpose</label>
|
||||
<input class="form-input" id="ids-purpose" type="text" bind:value={() => getProp("purpose"), (v) => setProp("purpose", v)} placeholder="Enter purpose">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Milestone</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("milestone"), (v) => setProp("milestone", v)} placeholder="Enter milestone">
|
||||
<label for="ids-milestone">Milestone</label>
|
||||
<input class="form-input" id="ids-milestone" type="text" bind:value={() => getProp("milestone"), (v) => setProp("milestone", v)} placeholder="Enter milestone">
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label>Copyright</label>
|
||||
<input class="form-input" type="text" bind:value={() => getProp("copyright"), (v) => setProp("copyright", v)} placeholder="Enter copyright">
|
||||
<label for="ids-copyright">Copyright</label>
|
||||
<input class="form-input" id="ids-copyright" type="text" bind:value={() => getProp("copyright"), (v) => setProp("copyright", v)} placeholder="Enter copyright">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import { getAuditReportById, downloadAuditReport } from "$src/modules/api/api.svelte.js";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import { getAuditReportById, downloadAuditReport } from "$src/modules/api/api.svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
import * as Tooltip from "$src/lib/components/ui/tooltip";
|
||||
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||
import type { DocumentState, Facet, IdsDocument, Specification } from "$src/types/ids";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let auditReport = $derived(documentState?.auditReport ? getAuditReportById(documentState.auditReport) : null);
|
||||
let expandedSpecs = $state(new Set());
|
||||
let expandedRequirements = $state(new Set());
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let auditReport = $derived(
|
||||
documentState?.auditReport ? (getAuditReportById(documentState.auditReport) as AuditReport | undefined) : null
|
||||
);
|
||||
let expandedSpecs = $state(new Set<number>());
|
||||
let expandedRequirements = $state(new Set<string>());
|
||||
let allExpanded = $state(false);
|
||||
|
||||
type SpecificationStatus = boolean | 'skipped' | null;
|
||||
|
||||
// Open Editor mode and jump to a specific specification
|
||||
function editSpecification(index) {
|
||||
function editSpecification(index: number) {
|
||||
if (IDS.Module.activeDocument) {
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, {
|
||||
viewMode: 'editor',
|
||||
@@ -23,13 +33,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSpecification(index) {
|
||||
function toggleSpecification(index: number) {
|
||||
if (expandedSpecs.has(index)) {
|
||||
expandedSpecs.delete(index);
|
||||
} else {
|
||||
expandedSpecs.add(index);
|
||||
}
|
||||
expandedSpecs = new Set(expandedSpecs);
|
||||
expandedSpecs = new Set<number>(expandedSpecs);
|
||||
}
|
||||
|
||||
function toggleAllSpecifications() {
|
||||
@@ -37,7 +47,7 @@
|
||||
|
||||
if (allExpanded) {
|
||||
// Collapse all
|
||||
expandedSpecs = new Set();
|
||||
expandedSpecs = new Set<number>();
|
||||
allExpanded = false;
|
||||
} else {
|
||||
// Expand all
|
||||
@@ -47,43 +57,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getSpecificationStatus(specIndex, auditData) {
|
||||
function getSpecificationStatus(specIndex: number, auditData: AuditReportData): SpecificationStatus {
|
||||
const spec = auditData.specifications[specIndex];
|
||||
if (!spec) return null;
|
||||
|
||||
// If no applicable elements and no checks, and it passed, it's actually skipped
|
||||
if (spec.total_applicable === 0 && spec.total_checks === 0 && spec.status === true) {
|
||||
return 'skipped';
|
||||
}
|
||||
|
||||
return spec.status;
|
||||
return spec.is_skipped ? 'skipped' : spec.status;
|
||||
}
|
||||
|
||||
function getSpecificationStats(specIndex, auditData) {
|
||||
function getSpecificationStats(specIndex: number, auditData: AuditReportData) {
|
||||
const spec = auditData.specifications[specIndex];
|
||||
if (!spec) return null;
|
||||
return {
|
||||
requirements: `${spec.total_requirements || 0}`,
|
||||
requirementsPassed: `${spec.total_requirements_pass || 0}`,
|
||||
checksTotal: `${spec.total_checks || 0}`,
|
||||
checksPassed: `${spec.total_checks_pass || 0}`,
|
||||
applicableTotal: `${spec.total_applicable || 0}`,
|
||||
applicablePassed: `${spec.total_applicable_pass || 0}`
|
||||
requirements: spec.total_requirements || 0,
|
||||
requirementsPassed: spec.total_requirements_pass || 0,
|
||||
checksTotal: spec.total_checks || 0,
|
||||
checksPassed: spec.total_checks_pass || 0,
|
||||
applicableTotal: spec.total_applicable || 0,
|
||||
applicablePassed: spec.total_applicable_pass || 0
|
||||
};
|
||||
}
|
||||
|
||||
function getSpecificationReason(specIndex, auditData) {
|
||||
function getSpecificationReason(specIndex: number, auditData: AuditReportData) {
|
||||
const spec = auditData.specifications[specIndex];
|
||||
if (!spec) return null;
|
||||
|
||||
const status = getSpecificationStatus(specIndex, auditData);
|
||||
|
||||
if (status === 'skipped') {
|
||||
return "Skipped because no applicable entities were found and the cardinality is OPTIONAL or PROHIBITED";
|
||||
return "Skipped because no applicable entities were found and the cardinality is OPTIONAL";
|
||||
}
|
||||
|
||||
if (status === false) { // Failed
|
||||
if (spec.total_applicable === 0) {
|
||||
if (spec.cardinality === 'prohibited') {
|
||||
return `Failed because ${spec.total_applicable} prohibited entities were found`;
|
||||
} else if (spec.total_applicable === 0) {
|
||||
return "Failed because no applicable entities were found but the cardinality is REQUIRED";
|
||||
} else {
|
||||
const failedChecks = spec.total_checks - spec.total_checks_pass;
|
||||
@@ -94,6 +100,10 @@
|
||||
return null; // No reason needed for passed specifications
|
||||
}
|
||||
|
||||
function getDocumentSpecificationUsage(spec: Specification) {
|
||||
return IDS.getSpecUsage(spec);
|
||||
}
|
||||
|
||||
async function handleDownloadReport() {
|
||||
if (!auditReport) return;
|
||||
|
||||
@@ -101,29 +111,62 @@
|
||||
await downloadAuditReport(auditReport.id);
|
||||
success('Audit report downloaded successfully');
|
||||
} catch (err) {
|
||||
error(`Failed to download report: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to download report: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getRequirementStatus(specIndex, reqIndex, auditData) {
|
||||
function getRequirementStatus(specIndex: number, reqIndex: number, auditData: AuditReportData) {
|
||||
const spec = auditData.specifications[specIndex];
|
||||
if (!spec || !spec.requirements || !spec.requirements[reqIndex]) return null;
|
||||
return spec.requirements[reqIndex];
|
||||
}
|
||||
|
||||
function toggleRequirementDetails(specIndex, reqIndex) {
|
||||
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}`;
|
||||
if (expandedRequirements.has(key)) {
|
||||
expandedRequirements.delete(key);
|
||||
} else {
|
||||
expandedRequirements.add(key);
|
||||
}
|
||||
expandedRequirements = new Set(expandedRequirements);
|
||||
expandedRequirements = new Set<string>(expandedRequirements);
|
||||
}
|
||||
|
||||
function isRequirementDetailsExpanded(specIndex, reqIndex) {
|
||||
function isRequirementDetailsExpanded(specIndex: number, reqIndex: number) {
|
||||
return expandedRequirements.has(`${specIndex}-${reqIndex}`);
|
||||
}
|
||||
|
||||
const handleActivation = (event: KeyboardEvent, action: () => void) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
action();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="ids-viewer">
|
||||
@@ -211,8 +254,17 @@
|
||||
</button>
|
||||
</div>
|
||||
{#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="spec-card-header" onclick={() => toggleSpecification(index)}>
|
||||
<div
|
||||
class="spec-card-header"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-expanded={expandedSpecs.has(index)}
|
||||
onclick={() => toggleSpecification(index)}
|
||||
onkeydown={(event) => handleActivation(event, () => toggleSpecification(index))}
|
||||
>
|
||||
<div class="spec-title-section">
|
||||
<div class="spec-title-row">
|
||||
<h2>{spec["@name"] || `Specification ${index + 1}`}</h2>
|
||||
@@ -240,19 +292,30 @@
|
||||
{#if "@description" in spec}
|
||||
<p class="spec-description">{spec["@description"]}</p>
|
||||
{/if}
|
||||
<div class="spec-stats">
|
||||
{#if usage === 'required'}
|
||||
<span class="stat-item">Required</span>
|
||||
{/if}
|
||||
{#if usage === 'optional'}
|
||||
<span class="stat-item">Optional</span>
|
||||
{/if}
|
||||
{#if usage === 'prohibited'}
|
||||
<span class="stat-item">Prohibited</span>
|
||||
{/if}
|
||||
{#if auditReport}
|
||||
{@const stats = getSpecificationStats(index, auditReport.data)}
|
||||
{@const status = getSpecificationStatus(index, auditReport.data)}
|
||||
{#if stats && usage !== 'prohibited' && status !== 'skipped'}
|
||||
<span class="stat-item">Checks: {stats.checksPassed}/{stats.checksTotal}</span>
|
||||
<span class="stat-item">Requirements: {stats.requirementsPassed}/{stats.requirements}</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{#if auditReport}
|
||||
{@const reason = getSpecificationReason(index, auditReport.data)}
|
||||
{#if reason}
|
||||
<p class="spec-reason">{reason}</p>
|
||||
{/if}
|
||||
{@const stats = getSpecificationStats(index, auditReport.data)}
|
||||
{@const status = getSpecificationStatus(index, auditReport.data)}
|
||||
{#if stats && status !== 'skipped'}
|
||||
<div class="spec-stats">
|
||||
<span class="stat-item">Checks: {stats.checksPassed}/{stats.checksTotal}</span>
|
||||
<span class="stat-item">Requirements: {stats.requirementsPassed}/{stats.requirements}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="spec-actions">
|
||||
@@ -292,39 +355,135 @@
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if auditReport}
|
||||
{@const status = getSpecificationStatus(index, auditReport.data)}
|
||||
{#if status === false && usage === 'prohibited'}
|
||||
{@const specReport = auditReport.data.specifications[index]}
|
||||
{@const applicableEntities = specReport.applicable_entities ?? []}
|
||||
<div class="entity-tables">
|
||||
{#if applicableEntities.length > 0}
|
||||
<div class="entity-table-section fail">
|
||||
<h4>Failed Elements ({applicableEntities.length})</h4>
|
||||
<div class="entity-table-container">
|
||||
<Tooltip.Provider>
|
||||
<table class="entity-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Class</th>
|
||||
<th>PredefinedType</th>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Warning</th>
|
||||
<th>GlobalId</th>
|
||||
<th>Tag</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each applicableEntities.slice(0, 10) as entity}
|
||||
<tr>
|
||||
<td>{entity.class}</td>
|
||||
<td>{entity.predefined_type || '-'}</td>
|
||||
<td>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<div class="truncated-text">{entity.name || '-'}</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>{entity.name || '-'}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</td>
|
||||
<td>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<div class="truncated-text">{entity.description || '-'}</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>{entity.description || '-'}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</td>
|
||||
<td>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<div class="truncated-text">{entity.reason || '-'}</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>{entity.reason || '-'}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</td>
|
||||
<td>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<div class="truncated-text">{entity.global_id || '-'}</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>{entity.global_id || '-'}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</td>
|
||||
<td>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<div class="truncated-text">{entity.tag || '-'}</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>{entity.tag || '-'}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{#if applicableEntities.length > 10}
|
||||
<tr class="more-row">
|
||||
<td colspan="7">... {applicableEntities.length - 10} more failing elements not shown ...</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</Tooltip.Provider>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Requirements Section -->
|
||||
{#if requirementGroups.length > 0}
|
||||
<div class="facet-section">
|
||||
<h3>Requirements</h3>
|
||||
|
||||
<div class="facets-list">
|
||||
{#each Object.entries(spec.requirements || {}) as [facetType, facets]}
|
||||
{#if Array.isArray(facets) && facets.length > 0}
|
||||
<div class="facet-group">
|
||||
{#each facets as facet, facetIndex}
|
||||
{@const reqAuditData = auditReport ? getRequirementStatus(index, facetIndex, 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') : ''}">
|
||||
<button class="facet-header" onclick={() => {if (auditReport && reqAuditData && specStatus !== 'skipped') toggleRequirementDetails(index, facetIndex)}}>
|
||||
<span class="facet-bullet">•</span>
|
||||
<span class="facet-text">{@html IDS.stringifyFacet("requirements", facet, facetType, spec)}</span>
|
||||
{#if auditReport && reqAuditData && specStatus !== 'skipped'}
|
||||
{#if reqAuditData.total_applicable > 0}
|
||||
<div class="audit-details-toggle">
|
||||
{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, facetIndex)}>
|
||||
<polyline points="6,9 12,15 18,9"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="audit-status-badge">
|
||||
{reqAuditData.status ? 'PASS' : 'FAIL'}
|
||||
</span>
|
||||
{/if}
|
||||
{#each requirementGroups as group}
|
||||
<div class="facet-group">
|
||||
{#each group.items as item}
|
||||
{@const reqAuditData = auditReport ? getRequirementStatus(index, item.reqIndex, 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') : ''}">
|
||||
<button class="facet-header" onclick={() => {if (auditReport && reqAuditData && specStatus !== 'skipped') toggleRequirementDetails(index, item.reqIndex)}}>
|
||||
<span class="facet-bullet">•</span>
|
||||
<span class="facet-text">{@html IDS.stringifyFacet("requirements", item.facet, group.facetType, spec)}</span>
|
||||
{#if auditReport && reqAuditData && specStatus !== 'skipped'}
|
||||
{#if reqAuditData.total_applicable > 0}
|
||||
<div class="audit-details-toggle">
|
||||
{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)}>
|
||||
<polyline points="6,9 12,15 18,9"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="audit-status-badge">
|
||||
{reqAuditData.status ? 'PASS' : 'FAIL'}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
{#if isRequirementDetailsExpanded(index, facetIndex)}
|
||||
{/if}
|
||||
</button>
|
||||
{#if reqAuditData && isRequirementDetailsExpanded(index, item.reqIndex)}
|
||||
<div class="facet-expansion">
|
||||
<div class="entity-tables">
|
||||
{#if reqAuditData.passed_entities && reqAuditData.passed_entities.length > 0}
|
||||
@@ -489,14 +648,14 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1128,4 +1287,4 @@
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,37 +1,53 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import FacetEditor from './FacetEditor.svelte';
|
||||
import CreateFacetDropdown from "$src/components/CreateFacetDropdown.svelte";
|
||||
import type { DocumentState, Facet, IdsDocument, Specification } from "$src/types/ids";
|
||||
|
||||
let { activeTab } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let activeSpecification = $derived(
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null
|
||||
);
|
||||
|
||||
async function addFacet (facetType) {
|
||||
if (!activeSpecification) return;
|
||||
async function addFacet(facetType: FacetType) {
|
||||
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||
|
||||
await IDS.createFacet(
|
||||
IDS.Module.activeDocument,
|
||||
documentState.activeSpecification,
|
||||
documentState.activeSpecification ?? 0,
|
||||
"requirements",
|
||||
facetType
|
||||
);
|
||||
}
|
||||
|
||||
async function removeFacet(facetType, facetIndex) {
|
||||
if (!activeSpecification) return;
|
||||
async function removeFacet(facetType: FacetType, facetIndex: number) {
|
||||
if (!activeSpecification || !documentState || !IDS.Module.activeDocument) return;
|
||||
|
||||
await IDS.deleteFacet(
|
||||
IDS.Module.activeDocument,
|
||||
documentState.activeSpecification,
|
||||
documentState.activeSpecification ?? 0,
|
||||
"requirements",
|
||||
facetType,
|
||||
facetIndex
|
||||
);
|
||||
}
|
||||
|
||||
let requirementEntries = $derived(
|
||||
activeSpecification?.requirements
|
||||
? (Object.entries(activeSpecification.requirements).filter(([, facets]) =>
|
||||
Array.isArray(facets)
|
||||
) as Array<[FacetType, Facet[]]>)
|
||||
: []
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="restrictions-panel">
|
||||
@@ -40,8 +56,8 @@
|
||||
<CreateFacetDropdown {addFacet} />
|
||||
</div>
|
||||
<div class="restrictions-list">
|
||||
{#if activeSpecification?.requirements}
|
||||
{#each Object.entries(activeSpecification.requirements) as [facetType, facets]}
|
||||
{#if activeSpecification && requirementEntries.length > 0}
|
||||
{#each requirementEntries as [facetType, facets]}
|
||||
{#each facets as facet, index}
|
||||
<FacetEditor
|
||||
bind:facet={facets[index]}
|
||||
@@ -50,10 +66,9 @@
|
||||
activeTab="requirements"
|
||||
{removeFacet}
|
||||
{index}
|
||||
key={`${activeDocument}-${documentState?.activeSpecification}-requirements-${facetType}-${index}`}
|
||||
/>
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,86 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Svelecte from 'svelecte';
|
||||
import { getEntityClasses, getMaterialCategories, getClassificationSystems, getDataTypes, getPredefinedTypes, getEntityAttributes, getApplicablePsets } from '$src/modules/api/api.svelte.js';
|
||||
import * as IDS from '$src/modules/api/ids.svelte.js';
|
||||
import {
|
||||
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();
|
||||
|
||||
const isEntityNameField = autocomplete === 'entityName';
|
||||
const isMaterialField = autocomplete === 'material';
|
||||
const isClassificationSystemField = autocomplete === 'classificationSystem';
|
||||
const isPredefinedTypeField = autocomplete === 'predefinedType';
|
||||
const isAttributeNameField = autocomplete === 'attributeName';
|
||||
const isPropertySetField = autocomplete === 'propertySet';
|
||||
const isDataTypeField = autocomplete === 'dataType';
|
||||
type AutocompleteType =
|
||||
| 'entityName'
|
||||
| 'material'
|
||||
| 'classificationSystem'
|
||||
| 'predefinedType'
|
||||
| 'attributeName'
|
||||
| 'propertySet'
|
||||
| 'dataType'
|
||||
| null;
|
||||
|
||||
let {
|
||||
facet = $bindable<Facet>({}),
|
||||
fieldName,
|
||||
label,
|
||||
placeholder,
|
||||
autocomplete = null,
|
||||
isSpecialProp = false
|
||||
}: {
|
||||
facet: Facet;
|
||||
fieldName: string;
|
||||
label: string;
|
||||
placeholder: string;
|
||||
autocomplete?: AutocompleteType;
|
||||
isSpecialProp?: boolean;
|
||||
} = $props();
|
||||
|
||||
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
|
||||
let predefinedTypeOptions = $state([]);
|
||||
let predefinedTypeOptions: string[] = $state([]);
|
||||
|
||||
// Attribute Names autocompletions
|
||||
let attributeNameOptions = $state([]);
|
||||
let attributeNameOptions: string[] = $state([]);
|
||||
|
||||
// Property Sets autocompletions
|
||||
let propertySetOptions = $state([]);
|
||||
let propertySetOptions: string[] = $state([]);
|
||||
|
||||
// Get the active specification's IFC schemas
|
||||
const getIfcVersions = () => {
|
||||
const activeDocument = IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null;
|
||||
const documentState = IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null;
|
||||
const activeSpecification = activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null;
|
||||
|
||||
const versions = activeSpecification?.["@ifcVersion"] || ['IFC4'];
|
||||
// TODO: Fix: Filter out IFC4X3 because it's buggy
|
||||
return versions.filter(version => version !== 'IFC4X3_ADD2');
|
||||
@@ -36,7 +88,7 @@
|
||||
|
||||
// Get the entity name from facet
|
||||
const getEntityName = () => {
|
||||
const nameField = facet?.name;
|
||||
const nameField = (facet as Record<string, unknown>)?.name as FacetValue | undefined;
|
||||
if (!nameField) return '';
|
||||
if (nameField.simpleValue) return nameField.simpleValue;
|
||||
return '';
|
||||
@@ -44,18 +96,13 @@
|
||||
|
||||
// Get all entity names from Entity facets in Applicability
|
||||
const getApplicabilityEntityNames = () => {
|
||||
const activeDocument = IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null;
|
||||
const documentState = IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null;
|
||||
const activeSpecification = activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null;
|
||||
|
||||
if (!activeSpecification?.applicability?.entity) return [];
|
||||
|
||||
const entityFacets = activeSpecification.applicability.entity;
|
||||
const entityNames = [];
|
||||
const entityFacets = activeSpecification.applicability.entity as Facet[];
|
||||
const entityNames: string[] = [];
|
||||
|
||||
entityFacets.forEach(entityFacet => {
|
||||
const nameField = entityFacet.name;
|
||||
const nameField = (entityFacet as Record<string, unknown>).name as FacetValue | undefined;
|
||||
if (!nameField) return;
|
||||
|
||||
if (nameField.simpleValue) {
|
||||
@@ -74,19 +121,14 @@
|
||||
|
||||
// Get entity facets with their predefined types
|
||||
const getApplicabilityEntityFacets = () => {
|
||||
const activeDocument = IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null;
|
||||
const documentState = IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null;
|
||||
const activeSpecification = activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null;
|
||||
|
||||
if (!activeSpecification?.applicability?.entity) return [];
|
||||
|
||||
return activeSpecification.applicability.entity.map(entityFacet => {
|
||||
const nameField = entityFacet.name;
|
||||
const predefinedTypeField = entityFacet.predefinedType;
|
||||
return (activeSpecification.applicability.entity as Facet[]).map(entityFacet => {
|
||||
const nameField = (entityFacet as Record<string, unknown>).name as FacetValue | undefined;
|
||||
const predefinedTypeField = (entityFacet as Record<string, unknown>).predefinedType as FacetValue | undefined;
|
||||
|
||||
// Extract entity name
|
||||
let entityNames = [];
|
||||
let entityNames: string[] = [];
|
||||
if (nameField?.simpleValue) {
|
||||
entityNames = [nameField.simpleValue];
|
||||
} else if (nameField?.restriction?.enumeration) {
|
||||
@@ -99,9 +141,12 @@
|
||||
let predefinedType = '';
|
||||
if (predefinedTypeField?.simpleValue) {
|
||||
predefinedType = predefinedTypeField.simpleValue;
|
||||
} else if (predefinedTypeField?.restriction?.enumeration?.length > 0) {
|
||||
// For enumeration predefined types, use the first one or empty string
|
||||
predefinedType = predefinedTypeField.restriction.enumeration[0]['@value'] || '';
|
||||
} else {
|
||||
const enumValues = predefinedTypeField?.restriction?.enumeration;
|
||||
if (enumValues && enumValues.length > 0) {
|
||||
// For enumeration predefined types, use the first one or empty string
|
||||
predefinedType = enumValues[0]['@value'] || '';
|
||||
}
|
||||
}
|
||||
|
||||
return { entityNames, predefinedType };
|
||||
@@ -109,7 +154,8 @@
|
||||
};
|
||||
|
||||
// Contextual autocompletions
|
||||
$effect(async () => {
|
||||
$effect(() => {
|
||||
void (async () => {
|
||||
|
||||
// Predefined Types
|
||||
if (isPredefinedTypeField) {
|
||||
@@ -122,10 +168,10 @@
|
||||
const typePromises = ifcVersions.map(schema =>
|
||||
getPredefinedTypes(schema, entityName.toUpperCase())
|
||||
);
|
||||
const typeSets = await Promise.all(typePromises);
|
||||
const typeSets = await Promise.all(typePromises) as string[][];
|
||||
|
||||
// Deduplicate predefined types across all schemas
|
||||
const allTypes = new Set();
|
||||
const allTypes = new Set<string>();
|
||||
typeSets.forEach(types => {
|
||||
if (types && Array.isArray(types)) {
|
||||
types.forEach(type => allTypes.add(type));
|
||||
@@ -150,19 +196,19 @@
|
||||
if (entityNames.length > 0 && ifcVersions.length > 0) {
|
||||
try {
|
||||
// Fetch attributes for all entity names across all selected schemas
|
||||
const attributePromises = [];
|
||||
const attributePromises: Array<Promise<{ name: string }[]>> = [];
|
||||
entityNames.forEach(entityName => {
|
||||
ifcVersions.forEach(schema => {
|
||||
attributePromises.push(
|
||||
getEntityAttributes(schema, entityName.toUpperCase())
|
||||
getEntityAttributes(schema, entityName.toUpperCase()) as Promise<{ name: string }[]>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const attributeSets = await Promise.all(attributePromises);
|
||||
const attributeSets = await Promise.all(attributePromises) as { name: string }[][];
|
||||
|
||||
// Deduplicate attribute names across all entities and schemas
|
||||
const allAttributes = new Set();
|
||||
const allAttributes = new Set<string>();
|
||||
attributeSets.forEach(attributes => {
|
||||
if (attributes && Array.isArray(attributes)) {
|
||||
attributes.forEach(attr => {
|
||||
@@ -191,21 +237,21 @@
|
||||
if (entityFacets.length > 0 && ifcVersions.length > 0) {
|
||||
try {
|
||||
// Fetch applicable property sets for all entity facets across all selected schemas
|
||||
const psetPromises = [];
|
||||
const psetPromises: Array<Promise<string[]>> = [];
|
||||
entityFacets.forEach(facet => {
|
||||
facet.entityNames.forEach(entityName => {
|
||||
ifcVersions.forEach(schema => {
|
||||
psetPromises.push(
|
||||
getApplicablePsets(schema, entityName.toUpperCase(), facet.predefinedType)
|
||||
getApplicablePsets(schema, entityName.toUpperCase(), facet.predefinedType) as Promise<string[]>
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const psetSets = await Promise.all(psetPromises);
|
||||
const psetSets = await Promise.all(psetPromises) as string[][];
|
||||
|
||||
// Deduplicate property set names
|
||||
const allPsets = new Set();
|
||||
const allPsets = new Set<string>();
|
||||
psetSets.forEach(psets => {
|
||||
if (psets && Array.isArray(psets)) {
|
||||
psets.forEach(pset => allPsets.add(pset));
|
||||
@@ -221,10 +267,11 @@
|
||||
propertySetOptions = [];
|
||||
}
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
// Get autocomplete options based on field type
|
||||
const getAutocompleteOptions = () => {
|
||||
const getAutocompleteOptions = (): string[] => {
|
||||
if (isEntityNameField) return getEntityClasses();
|
||||
if (isMaterialField) return getMaterialCategories();
|
||||
if (isClassificationSystemField) return Object.keys(getClassificationSystems());
|
||||
@@ -236,11 +283,11 @@
|
||||
};
|
||||
|
||||
const getRestrictionType = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue) return 'Simple';
|
||||
if (fieldValue.simpleValue !== undefined) return 'Simple';
|
||||
if (fieldValue['restriction']) {
|
||||
const restriction = fieldValue['restriction'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
if (restriction['enumeration']) return 'Enumeration';
|
||||
if (restriction['pattern']) return 'Pattern';
|
||||
if (restriction['minInclusive'] || restriction['maxInclusive'] ||
|
||||
@@ -251,39 +298,40 @@
|
||||
return 'Simple';
|
||||
};
|
||||
|
||||
const getSimpleValue = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const getSimpleValue = (): string => {
|
||||
const fieldValue = getFieldValue();
|
||||
|
||||
// For special properties (eg. @dataType), we return the value directly
|
||||
if (isSpecialProp) return fieldValue || null;
|
||||
if (typeof fieldValue === 'string') return fieldValue;
|
||||
if (isSpecialProp) return '';
|
||||
|
||||
if (!fieldValue) return null;
|
||||
if (!fieldValue) return '';
|
||||
if (fieldValue.simpleValue !== undefined) return fieldValue.simpleValue;
|
||||
return null;
|
||||
return '';
|
||||
};
|
||||
|
||||
const getEnumerationValues = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return [''];
|
||||
const restriction = fieldValue['restriction'];
|
||||
const enumValues = restriction['enumeration'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const enumValues = restriction['enumeration'] as RestrictionValue[] | undefined;
|
||||
if (!enumValues) return [''];
|
||||
return enumValues.map(item => item['@value'] || '');
|
||||
};
|
||||
|
||||
const getPatternValue = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return '';
|
||||
const restriction = fieldValue['restriction'];
|
||||
const pattern = restriction['pattern'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const pattern = restriction['pattern'] as RestrictionValue[] | undefined;
|
||||
if (!pattern || !pattern.length) return '';
|
||||
return pattern[0]['@value'] || '';
|
||||
};
|
||||
|
||||
const getRangeValues = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return { min: '', max: '', minType: 'Inclusive', maxType: 'Inclusive' };
|
||||
const restriction = fieldValue['restriction'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
|
||||
let min = '', max = '', minType = 'Inclusive', maxType = 'Inclusive';
|
||||
|
||||
@@ -307,18 +355,18 @@
|
||||
};
|
||||
|
||||
const getLengthValue = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return '';
|
||||
const restriction = fieldValue['restriction'];
|
||||
const length = restriction['length'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const length = restriction['length'] as RestrictionValue[] | undefined;
|
||||
if (!length || !length.length) return '';
|
||||
return length[0]['@value'] || '';
|
||||
};
|
||||
|
||||
const getLengthRangeValues = () => {
|
||||
const fieldValue = facet[fieldName];
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return { min: '', max: '' };
|
||||
const restriction = fieldValue['restriction'];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
|
||||
let min = '', max = '';
|
||||
|
||||
@@ -332,77 +380,85 @@
|
||||
return { min, max };
|
||||
};
|
||||
|
||||
const setSimpleValue = (value) => {
|
||||
const setSimpleValue = (value: string) => {
|
||||
// For special properties (eg. @dataType), we set the value directly
|
||||
if (isSpecialProp) {
|
||||
facet[fieldName] = value;
|
||||
setFieldValue(value);
|
||||
return;
|
||||
}
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
facet[fieldName] = { simpleValue: value };
|
||||
setFieldValue({ simpleValue: value });
|
||||
};
|
||||
|
||||
const setEnumerationValues = (values) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
const setEnumerationValues = (values: string[]) => {
|
||||
const enumItems = values.filter(v => v && typeof v === 'string' && v.trim() !== '').map(v => ({ '@value': v }));
|
||||
facet[fieldName] = {
|
||||
setFieldValue({
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'enumeration': enumItems
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setPatternValue = (value) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
facet[fieldName] = {
|
||||
const setPatternValue = (value: string) => {
|
||||
setFieldValue({
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'pattern': [{ '@value': value }]
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setRangeValues = (min, max, minType, maxType) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
const restriction = { '@base': 'xs:string' };
|
||||
const setRangeValues = (min: string, max: string, minType: string, maxType: string) => {
|
||||
const restriction: Restriction = { '@base': 'xs:string' };
|
||||
|
||||
if (min !== '') {
|
||||
const minKey = minType === 'Inclusive' ? 'minInclusive' : 'minExclusive';
|
||||
restriction[minKey] = [{ '@value': min }];
|
||||
(restriction as Record<string, RestrictionValue[]>)[minKey] = [{ '@value': min }];
|
||||
}
|
||||
if (max !== '') {
|
||||
const maxKey = maxType === 'Inclusive' ? 'maxInclusive' : 'maxExclusive';
|
||||
restriction[maxKey] = [{ '@value': max }];
|
||||
(restriction as Record<string, RestrictionValue[]>)[maxKey] = [{ '@value': max }];
|
||||
}
|
||||
|
||||
facet[fieldName] = { 'restriction': restriction };
|
||||
setFieldValue({ 'restriction': restriction });
|
||||
};
|
||||
|
||||
const setLengthValue = (value) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
facet[fieldName] = {
|
||||
const setLengthValue = (value: string) => {
|
||||
setFieldValue({
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'length': [{ '@value': value }]
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setLengthRangeValues = (min, max) => {
|
||||
if (!facet[fieldName]) facet[fieldName] = {};
|
||||
const restriction = { '@base': 'xs:string' };
|
||||
const setLengthRangeValues = (min: string, max: string) => {
|
||||
const restriction: Restriction = { '@base': 'xs:string' };
|
||||
|
||||
if (min !== '') restriction['minLength'] = [{ '@value': min }];
|
||||
if (max !== '') restriction['maxLength'] = [{ '@value': max }];
|
||||
|
||||
facet[fieldName] = { 'restriction': restriction };
|
||||
setFieldValue({ 'restriction': restriction });
|
||||
};
|
||||
|
||||
let restrictionType = $derived(getRestrictionType());
|
||||
let restrictionType = $state(getRestrictionType());
|
||||
let hasUserSelectedType = $state(false);
|
||||
let lastFacetRef = $state(facet);
|
||||
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;
|
||||
|
||||
switch (newType) {
|
||||
@@ -435,23 +491,27 @@
|
||||
enumerationValues = [...enumerationValues, ''];
|
||||
};
|
||||
|
||||
const removeEnumerationValue = (index) => {
|
||||
const removeEnumerationValue = (index: number) => {
|
||||
enumerationValues = enumerationValues.filter((_, i) => i !== index);
|
||||
setEnumerationValues(enumerationValues);
|
||||
};
|
||||
|
||||
const updateEnumerationValue = (index, value) => {
|
||||
const updateEnumerationValue = (index: number, value: string) => {
|
||||
enumerationValues[index] = value;
|
||||
setEnumerationValues(enumerationValues);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{label}</label>
|
||||
<div class="restriction-controls">
|
||||
<span class="form-label" id={`${baseId}-label`}>{label}</span>
|
||||
<div class="restriction-controls" role="group" aria-labelledby={`${baseId}-label`}>
|
||||
{#if !isSpecialProp}
|
||||
<div class="restriction-type-selector">
|
||||
<select class="form-input" bind:value={restrictionType} onchange={(e) => handleTypeChange(e.target.value)}>
|
||||
<select
|
||||
class="form-input"
|
||||
bind:value={restrictionType}
|
||||
onchange={(e) => handleTypeChange((e.target as HTMLSelectElement).value)}
|
||||
>
|
||||
<option value="Simple">Simple</option>
|
||||
<option value="Enumeration">Enumeration</option>
|
||||
<option value="Pattern">Pattern</option>
|
||||
@@ -477,7 +537,7 @@
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{:else}
|
||||
<input class="form-input" type="text" bind:value={() => getSimpleValue(), (v) => setSimpleValue(v)} {placeholder}>
|
||||
<input class="form-input" type="text" bind:value={() => getSimpleValue(), (v) => setSimpleValue(v)} {placeholder} aria-label={label}>
|
||||
{/if}
|
||||
|
||||
{:else if restrictionType === 'Enumeration'}
|
||||
@@ -497,7 +557,14 @@
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{:else}
|
||||
<input class="form-input" type="text" value={value} oninput={(e) => updateEnumerationValue(index, e.target.value)} {placeholder}>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
value={value}
|
||||
oninput={(e) => updateEnumerationValue(index, (e.target as HTMLInputElement).value)}
|
||||
{placeholder}
|
||||
aria-label={`${label} option ${index + 1}`}
|
||||
>
|
||||
{/if}
|
||||
<button class="btn-delete" onclick={() => removeEnumerationValue(index)} type="button" aria-label="Remove enumeration value">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -515,22 +582,43 @@
|
||||
</div>
|
||||
|
||||
{:else if restrictionType === 'Pattern'}
|
||||
<input class="form-input" type="text" bind:value={() => getPatternValue(), (v) => setPatternValue(v)} placeholder="Enter regex pattern (e.g., DT[0-9]{2})">
|
||||
<input class="form-input" type="text" bind:value={() => getPatternValue(), (v) => setPatternValue(v)} placeholder="Enter regex pattern (e.g., DT[0-9]{2})" aria-label={`${label} pattern`}>
|
||||
|
||||
{:else if restrictionType === 'Range'}
|
||||
{@const range = getRangeValues()}
|
||||
<div class="range-controls">
|
||||
<div class="range-group">
|
||||
<label>Min</label>
|
||||
<input class="form-input" type="text" bind:value={() => getRangeValues().min, (v) => { const range = getRangeValues(); setRangeValues(v, range.max, range.minType, range.maxType); }} placeholder="0">
|
||||
<select class="form-input" bind:value={() => getRangeValues().minType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, v, range.maxType); }}>
|
||||
<label for={`${baseId}-range-min`}>Min</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
id={`${baseId}-range-min`}
|
||||
bind:value={() => 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="Exclusive">Exclusive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="range-group">
|
||||
<label>Max</label>
|
||||
<input class="form-input" type="text" bind:value={() => getRangeValues().max, (v) => { const range = getRangeValues(); setRangeValues(range.min, v, range.minType, range.maxType); }} placeholder="0">
|
||||
<select class="form-input" bind:value={() => getRangeValues().maxType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, range.minType, v); }}>
|
||||
<label for={`${baseId}-range-max`}>Max</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
id={`${baseId}-range-max`}
|
||||
bind:value={() => 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="Exclusive">Exclusive</option>
|
||||
</select>
|
||||
@@ -538,17 +626,18 @@
|
||||
</div>
|
||||
|
||||
{:else if restrictionType === 'Length'}
|
||||
<input class="form-input" type="number" bind:value={() => getLengthValue(), (v) => setLengthValue(v)} placeholder="Enter exact length">
|
||||
<input class="form-input" type="number" bind:value={() => getLengthValue(), (v) => setLengthValue(v)} placeholder="Enter exact length" aria-label={`${label} length`}>
|
||||
|
||||
{:else if restrictionType === 'Length Range'}
|
||||
{@const lengthRange = getLengthRangeValues()}
|
||||
<div class="length-range-controls">
|
||||
<div class="length-group">
|
||||
<label>Min Length</label>
|
||||
<input class="form-input" type="number" bind:value={() => getLengthRangeValues().min, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(v, range.max); }} placeholder="0">
|
||||
<label for={`${baseId}-length-min`}>Min Length</label>
|
||||
<input class="form-input" id={`${baseId}-length-min`} type="number" bind:value={() => lengthRange.min, (v) => setLengthRangeValues(v, lengthRange.max)} placeholder="0">
|
||||
</div>
|
||||
<div class="length-group">
|
||||
<label>Max Length</label>
|
||||
<input class="form-input" type="number" bind:value={() => getLengthRangeValues().max, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(range.min, v); }} placeholder="0">
|
||||
<label for={`${baseId}-length-max`}>Max Length</label>
|
||||
<input class="form-input" id={`${baseId}-length-max`} type="number" bind:value={() => lengthRange.max, (v) => setLengthRangeValues(lengthRange.min, v)} placeholder="0">
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -656,4 +745,4 @@
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,44 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import type { DocumentState, IdsCardinality, IdsDocument, Specification } from "$src/types/ids";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let activeSpecification = $derived(
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null
|
||||
);
|
||||
|
||||
const getProp = (prop) => {
|
||||
return activeSpecification?.[prop] ?? "";
|
||||
const getProp = (prop: string) => {
|
||||
const value = (activeSpecification as Record<string, unknown>)?.[prop];
|
||||
return typeof value === "string" ? value : "";
|
||||
};
|
||||
|
||||
const setProp = (prop, value) => {
|
||||
activeSpecification[prop] = value;
|
||||
const setProp = (prop: string, value: string) => {
|
||||
if (!activeSpecification) return;
|
||||
(activeSpecification as Record<string, unknown>)[prop] = value;
|
||||
};
|
||||
|
||||
const addIfcVersion = (e, version) => {
|
||||
const addIfcVersion = (e: Event, version: string) => {
|
||||
if (activeSpecification) {
|
||||
if (!("@ifcVersion" in activeSpecification)) activeSpecification["@ifcVersion"] = [];
|
||||
if (e.target.checked) {
|
||||
if (!activeSpecification["@ifcVersion"].includes(version)) {
|
||||
activeSpecification["@ifcVersion"] = [...activeSpecification["@ifcVersion"], version];
|
||||
const target = e.target as HTMLInputElement | null;
|
||||
if (target?.checked) {
|
||||
if (!activeSpecification["@ifcVersion"]?.includes(version)) {
|
||||
activeSpecification["@ifcVersion"] = [...(activeSpecification["@ifcVersion"] ?? []), version];
|
||||
}
|
||||
} else {
|
||||
} else if (activeSpecification["@ifcVersion"]) {
|
||||
activeSpecification["@ifcVersion"] = activeSpecification["@ifcVersion"].filter(v => v !== version);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setUsage = (usage) => {
|
||||
const setUsage = (usage: IdsCardinality) => {
|
||||
if (!activeSpecification) return;
|
||||
if (!activeSpecification.applicability) activeSpecification.applicability = {};
|
||||
|
||||
@@ -60,14 +71,19 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spec-cardinality">Usage</label>
|
||||
<select class="form-input" id="spec-cardinality" value={IDS.getSpecUsage(activeSpecification)} onchange={(e) => setUsage(e.target.value)}>
|
||||
<select
|
||||
class="form-input"
|
||||
id="spec-cardinality"
|
||||
value={IDS.getSpecUsage(activeSpecification)}
|
||||
onchange={(e) => setUsage((e.target as HTMLSelectElement).value as IdsCardinality)}
|
||||
>
|
||||
<option value="required">Required</option>
|
||||
<option value="optional">Optional</option>
|
||||
<option value="prohibited">Prohibited</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label>IFC Version</label>
|
||||
<span class="form-label">IFC Version</span>
|
||||
<div class="radio-group">
|
||||
<label class="radio-label">
|
||||
<input type="checkbox" value="IFC2X3" checked={activeSpecification?.["@ifcVersion"]?.includes('IFC2X3')} onchange={(e) => addIfcVersion(e, 'IFC2X3')}>
|
||||
@@ -92,4 +108,4 @@
|
||||
<textarea class="form-input" id="spec-instructions" bind:value={() => getProp("@instructions"), (v) => setProp("@instructions", v)} placeholder="Enter instructions" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import AppHeader from "$src/components/AppHeader.svelte";
|
||||
import AppRibbon from "$src/components/AppRibbon.svelte";
|
||||
import AppToolbar from "$src/components/AppToolbar.svelte";
|
||||
@@ -11,14 +11,26 @@
|
||||
import IdsViewer from "./IdsViewer.svelte";
|
||||
import SplashScreen from "$src/components/SplashScreen.svelte";
|
||||
import { Toaster } from "$lib/components/ui/sonner";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
||||
import {onMount} from "svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
import type { DocumentState, IdsDocument, Specification } from "$src/types/ids";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
|
||||
let activeDocument = $derived(IDS.Module.activeDocument ? IDS.Module.documents[IDS.Module.activeDocument] : null);
|
||||
let documentState = $derived(IDS.Module.activeDocument ? IDS.Module.states[IDS.Module.activeDocument] : null);
|
||||
let activeSpecification = $derived(activeDocument && documentState?.activeSpecification !== null && activeDocument.specifications?.specification ?
|
||||
activeDocument.specifications.specification[documentState.activeSpecification] : null);
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
);
|
||||
let documentState = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
|
||||
);
|
||||
let activeSpecification = $derived(
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null
|
||||
);
|
||||
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() {
|
||||
if (!IDS.Module.activeDocument) return;
|
||||
@@ -28,11 +40,11 @@
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'info' });
|
||||
}
|
||||
|
||||
async function importSpecification(sourceDocId, specIndex) {
|
||||
async function importSpecification(sourceDocId: string, specIndex: number) {
|
||||
if (!IDS.Module.activeDocument || !IDS.Module.documents[sourceDocId]) return;
|
||||
|
||||
try {
|
||||
const sourceDoc = IDS.Module.documents[sourceDocId];
|
||||
const sourceDoc = IDS.Module.documents[sourceDocId] as IdsDocument;
|
||||
const sourceSpec = sourceDoc.specifications?.specification?.[specIndex];
|
||||
|
||||
if (!sourceSpec) {
|
||||
@@ -41,13 +53,13 @@
|
||||
}
|
||||
|
||||
// Create a deep copy of the specification
|
||||
const specCopy = JSON.parse(JSON.stringify(sourceSpec));
|
||||
const specCopy = JSON.parse(JSON.stringify(sourceSpec)) as Specification;
|
||||
|
||||
// Add the copied specification to the current document
|
||||
IDS.Module.documents[IDS.Module.activeDocument].specifications.specification.push(specCopy);
|
||||
|
||||
// Switch to the newly created specification and info tab
|
||||
const currentDoc = IDS.Module.documents[IDS.Module.activeDocument];
|
||||
const currentDoc = IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument;
|
||||
const newSpecIndex = (currentDoc.specifications?.specification?.length || 1) - 1;
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, {
|
||||
activeSpecification: newSpecIndex,
|
||||
@@ -57,17 +69,18 @@
|
||||
success(`Specification "${sourceSpec['@name'] || 'Unnamed'}" imported successfully`);
|
||||
} catch (err) {
|
||||
console.error('Error importing specification:', err);
|
||||
error(`Failed to import specification: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to import specification: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function selectSpecification(index) {
|
||||
function selectSpecification(index: number) {
|
||||
if (IDS.Module.activeDocument) {
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: index });
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSpecification(specIndex) {
|
||||
async function deleteSpecification(specIndex: number) {
|
||||
if (!IDS.Module.activeDocument) return;
|
||||
await IDS.deleteSpecification(IDS.Module.activeDocument, specIndex);
|
||||
}
|
||||
@@ -83,9 +96,24 @@
|
||||
success('IDS document exported successfully');
|
||||
} catch (err) {
|
||||
console.error('Error exporting IDS:', err);
|
||||
error('Error exporting IDS: ' + err.message);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error('Error exporting IDS: ' + message);
|
||||
}
|
||||
}
|
||||
|
||||
const updateActiveDocumentState = (updates: Partial<DocumentState>) => {
|
||||
if (IDS.Module.activeDocument) {
|
||||
IDS.setDocumentState(IDS.Module.activeDocument, updates);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActivation = (event: KeyboardEvent, action: () => void) => {
|
||||
if (event.currentTarget !== event.target) return;
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
action();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if IDS.Module.status != "ready"}
|
||||
@@ -130,8 +158,7 @@
|
||||
Import from IDS
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent class="w-64 max-h-64 overflow-y-auto">
|
||||
{#each Object.entries(IDS.Module.documents) as [docId, doc]}
|
||||
{#if docId !== IDS.Module.activeDocument && doc.specifications?.specification?.length > 0}
|
||||
{#each importableDocuments as [docId, doc], docIndex}
|
||||
<DropdownMenu.Label class="font-medium text-xs text-muted-foreground px-2 py-1 truncate">
|
||||
{doc.info?.title || 'Untitled Document'}
|
||||
</DropdownMenu.Label>
|
||||
@@ -146,12 +173,11 @@
|
||||
</span>
|
||||
</DropdownMenu.Item>
|
||||
{/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 />
|
||||
{/if}
|
||||
{/if}
|
||||
{/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>
|
||||
<span class="text-sm">No specifications available to import</span>
|
||||
</DropdownMenu.Item>
|
||||
@@ -162,16 +188,30 @@
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
<div class="specifications-list scrollbar">
|
||||
<div class="spec-item" class:active={documentState?.activeSpecification === null} onclick={() => { if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); }}>
|
||||
<div
|
||||
class="spec-item"
|
||||
class:active={documentState?.activeSpecification === null}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => { if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); }}
|
||||
onkeydown={(event) => handleActivation(event, () => { if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); })}
|
||||
>
|
||||
<span class="spec-icon">ℹ️</span>
|
||||
<span class="spec-name">IDS Information</span>
|
||||
</div>
|
||||
{#if activeDocument?.specifications?.specification}
|
||||
{#each activeDocument.specifications.specification as spec, index}
|
||||
<div class="spec-item" class:active={documentState?.activeSpecification === index} onclick={() => selectSpecification(index)}>
|
||||
<div
|
||||
class="spec-item"
|
||||
class:active={documentState?.activeSpecification === index}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => selectSpecification(index)}
|
||||
onkeydown={(event) => handleActivation(event, () => selectSpecification(index))}
|
||||
>
|
||||
<span class="spec-icon">📄</span>
|
||||
<span class="spec-name">{spec["@name"] || "Specification " + (index + 1)}</span>
|
||||
<button class="btn-delete" onclick={() => deleteSpecification(index)}>
|
||||
<button class="btn-delete" onclick={() => deleteSpecification(index)} aria-label="Delete specification">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 6L6 18M6 6l12 12"></path>
|
||||
</svg>
|
||||
@@ -192,10 +232,10 @@
|
||||
{:else}
|
||||
<!-- Editor/Viewer Toggle -->
|
||||
<div class="view-mode-toggle">
|
||||
<button class="toggle-btn" class:active={documentState?.viewMode === 'editor'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { viewMode: 'editor', auditReport: null })}>
|
||||
<button class="toggle-btn" class:active={documentState?.viewMode === 'editor'} onclick={() => updateActiveDocumentState({ viewMode: 'editor', auditReport: null })}>
|
||||
Editor
|
||||
</button>
|
||||
<button class="toggle-btn" class:active={documentState?.viewMode === 'viewer'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { viewMode: 'viewer', auditReport: null })}>
|
||||
<button class="toggle-btn" class:active={documentState?.viewMode === 'viewer'} onclick={() => updateActiveDocumentState({ viewMode: 'viewer', auditReport: null })}>
|
||||
Viewer
|
||||
</button>
|
||||
</div>
|
||||
@@ -209,18 +249,18 @@
|
||||
<div class="spec-header">
|
||||
<h2>{activeSpecification ? activeSpecification["@name"] || "Specification" : "Specification"}</h2>
|
||||
<div class="spec-tabs">
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'info'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'info' })}>Info</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'applicability'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'applicability' })}>Applicability</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'requirements'} onclick={() => IDS.setDocumentState(IDS.Module.activeDocument, { activeTab: 'requirements' })}>Requirements</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'info'} onclick={() => updateActiveDocumentState({ activeTab: 'info' })}>Info</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'applicability'} onclick={() => updateActiveDocumentState({ activeTab: 'applicability' })}>Applicability</button>
|
||||
<button class="btn tab-btn" class:active={documentState?.activeTab === 'requirements'} onclick={() => updateActiveDocumentState({ activeTab: 'requirements' })}>Requirements</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if documentState?.activeTab === 'info'}
|
||||
<SpecificationEditor />
|
||||
{:else if documentState?.activeTab === 'applicability'}
|
||||
<ApplicabilityPanel activeTab />
|
||||
<ApplicabilityPanel />
|
||||
{:else if documentState?.activeTab === 'requirements'}
|
||||
<RequirementsPanel activeTab />
|
||||
<RequirementsPanel />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
export type IdsCardinality = "required" | "optional" | "prohibited";
|
||||
|
||||
export type RestrictionValue = {
|
||||
"@value": string;
|
||||
};
|
||||
|
||||
export type Restriction = {
|
||||
"@base"?: string;
|
||||
enumeration?: RestrictionValue[];
|
||||
pattern?: RestrictionValue[];
|
||||
length?: RestrictionValue[];
|
||||
minLength?: RestrictionValue[];
|
||||
maxLength?: RestrictionValue[];
|
||||
minInclusive?: RestrictionValue[];
|
||||
maxInclusive?: RestrictionValue[];
|
||||
minExclusive?: RestrictionValue[];
|
||||
maxExclusive?: RestrictionValue[];
|
||||
};
|
||||
|
||||
export type SimpleValue = {
|
||||
simpleValue: string;
|
||||
};
|
||||
|
||||
export type FacetValue = {
|
||||
simpleValue?: string;
|
||||
restriction?: Restriction;
|
||||
};
|
||||
|
||||
export type Facet = Record<string, FacetValue | string | number | boolean | null | undefined>;
|
||||
|
||||
export type FacetClause = Record<string, Facet[] | number | "unbounded" | undefined>;
|
||||
|
||||
export type Specification = {
|
||||
"@name"?: string;
|
||||
"@identifier"?: string;
|
||||
"@description"?: string;
|
||||
"@instructions"?: string;
|
||||
"@ifcVersion"?: string[];
|
||||
applicability?: FacetClause;
|
||||
requirements?: FacetClause;
|
||||
};
|
||||
|
||||
export type IdsInfo = {
|
||||
title?: string;
|
||||
copyright?: string;
|
||||
version?: string;
|
||||
description?: string;
|
||||
author?: string;
|
||||
date?: string;
|
||||
purpose?: string;
|
||||
milestone?: string;
|
||||
};
|
||||
|
||||
export type IdsDocument = {
|
||||
"@xmlns"?: string;
|
||||
"@xmlns:xs"?: string;
|
||||
"@xmlns:xsi"?: string;
|
||||
"@xsi:schemaLocation"?: string;
|
||||
info: IdsInfo;
|
||||
specifications: {
|
||||
specification: Specification[];
|
||||
};
|
||||
};
|
||||
|
||||
export type DocumentState = {
|
||||
activeTab: "info" | "applicability" | "requirements";
|
||||
viewMode: "editor" | "viewer";
|
||||
activeSpecification: number | null;
|
||||
auditReport?: string | null;
|
||||
};
|
||||
@@ -0,0 +1,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 { defineConfig } from 'vite';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import path from "path";
|
||||
import path from "node:path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), svelte()],
|
||||
@@ -11,4 +11,4 @@ export default defineConfig({
|
||||
$src: path.resolve("./src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user