mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
fixes and cleanups from old branch
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
import * as Tooltip from "$src/lib/components/ui/tooltip";
|
||||
import type { AuditReport, AuditReportData } from "$src/types/report";
|
||||
import type { DocumentState, IdsDocument, Specification } from "$src/types/ids";
|
||||
import type { DocumentState, Facet, IdsDocument, Specification } from "$src/types/ids";
|
||||
|
||||
let activeDocument = $derived(
|
||||
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
|
||||
@@ -122,6 +122,31 @@
|
||||
return spec.requirements[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)) {
|
||||
@@ -230,6 +255,7 @@
|
||||
</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"
|
||||
@@ -428,37 +454,36 @@
|
||||
</div>
|
||||
|
||||
<!-- Requirements Section -->
|
||||
{#if Array.isArray(spec.requirements) && spec.requirements.length > 0}
|
||||
{#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 reqAuditData && 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}
|
||||
@@ -623,11 +648,10 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,6 +48,27 @@
|
||||
|
||||
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: string[] = $state([]);
|
||||
@@ -60,17 +81,6 @@
|
||||
|
||||
// Get the active specification's IFC schemas
|
||||
const getIfcVersions = () => {
|
||||
const activeDocument = IDS.Module.activeDocument
|
||||
? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument)
|
||||
: null;
|
||||
const documentState = IDS.Module.activeDocument
|
||||
? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState)
|
||||
: null;
|
||||
const activeSpecification =
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null;
|
||||
|
||||
const versions = activeSpecification?.["@ifcVersion"] || ['IFC4'];
|
||||
// TODO: Fix: Filter out IFC4X3 because it's buggy
|
||||
return versions.filter(version => version !== 'IFC4X3_ADD2');
|
||||
@@ -86,17 +96,6 @@
|
||||
|
||||
// Get all entity names from Entity facets in Applicability
|
||||
const getApplicabilityEntityNames = () => {
|
||||
const activeDocument = IDS.Module.activeDocument
|
||||
? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument)
|
||||
: null;
|
||||
const documentState = IDS.Module.activeDocument
|
||||
? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState)
|
||||
: null;
|
||||
const activeSpecification =
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null;
|
||||
|
||||
if (!activeSpecification?.applicability?.entity) return [];
|
||||
|
||||
const entityFacets = activeSpecification.applicability.entity as Facet[];
|
||||
@@ -122,17 +121,6 @@
|
||||
|
||||
// Get entity facets with their predefined types
|
||||
const getApplicabilityEntityFacets = () => {
|
||||
const activeDocument = IDS.Module.activeDocument
|
||||
? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument)
|
||||
: null;
|
||||
const documentState = IDS.Module.activeDocument
|
||||
? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState)
|
||||
: null;
|
||||
const activeSpecification =
|
||||
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
|
||||
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
|
||||
: null;
|
||||
|
||||
if (!activeSpecification?.applicability?.entity) return [];
|
||||
|
||||
return (activeSpecification.applicability.entity as Facet[]).map(entityFacet => {
|
||||
@@ -295,7 +283,7 @@
|
||||
};
|
||||
|
||||
const getRestrictionType = () => {
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue) return 'Simple';
|
||||
if (fieldValue.simpleValue !== undefined) return 'Simple';
|
||||
if (fieldValue['restriction']) {
|
||||
@@ -311,7 +299,7 @@
|
||||
};
|
||||
|
||||
const getSimpleValue = (): string => {
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | string | undefined;
|
||||
const fieldValue = getFieldValue();
|
||||
|
||||
// For special properties (eg. @dataType), we return the value directly
|
||||
if (typeof fieldValue === 'string') return fieldValue;
|
||||
@@ -323,7 +311,7 @@
|
||||
};
|
||||
|
||||
const getEnumerationValues = () => {
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return [''];
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const enumValues = restriction['enumeration'] as RestrictionValue[] | undefined;
|
||||
@@ -332,7 +320,7 @@
|
||||
};
|
||||
|
||||
const getPatternValue = () => {
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return '';
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const pattern = restriction['pattern'] as RestrictionValue[] | undefined;
|
||||
@@ -341,7 +329,7 @@
|
||||
};
|
||||
|
||||
const getRangeValues = () => {
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return { min: '', max: '', minType: 'Inclusive', maxType: 'Inclusive' };
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
|
||||
@@ -367,7 +355,7 @@
|
||||
};
|
||||
|
||||
const getLengthValue = () => {
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return '';
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
const length = restriction['length'] as RestrictionValue[] | undefined;
|
||||
@@ -376,7 +364,7 @@
|
||||
};
|
||||
|
||||
const getLengthRangeValues = () => {
|
||||
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined;
|
||||
const fieldValue = getFacetValue();
|
||||
if (!fieldValue?.['restriction']) return { min: '', max: '' };
|
||||
const restriction = fieldValue['restriction'] as Restriction;
|
||||
|
||||
@@ -395,29 +383,29 @@
|
||||
const setSimpleValue = (value: string) => {
|
||||
// For special properties (eg. @dataType), we set the value directly
|
||||
if (isSpecialProp) {
|
||||
(facet as Record<string, unknown>)[fieldName] = value;
|
||||
setFieldValue(value);
|
||||
return;
|
||||
}
|
||||
(facet as Record<string, unknown>)[fieldName] = { simpleValue: value };
|
||||
setFieldValue({ simpleValue: value });
|
||||
};
|
||||
|
||||
const setEnumerationValues = (values: string[]) => {
|
||||
const enumItems = values.filter(v => v && typeof v === 'string' && v.trim() !== '').map(v => ({ '@value': v }));
|
||||
(facet as Record<string, unknown>)[fieldName] = {
|
||||
setFieldValue({
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'enumeration': enumItems
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setPatternValue = (value: string) => {
|
||||
(facet as Record<string, unknown>)[fieldName] = {
|
||||
setFieldValue({
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'pattern': [{ '@value': value }]
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setRangeValues = (min: string, max: string, minType: string, maxType: string) => {
|
||||
@@ -432,16 +420,16 @@
|
||||
(restriction as Record<string, RestrictionValue[]>)[maxKey] = [{ '@value': max }];
|
||||
}
|
||||
|
||||
(facet as Record<string, unknown>)[fieldName] = { 'restriction': restriction };
|
||||
setFieldValue({ 'restriction': restriction });
|
||||
};
|
||||
|
||||
const setLengthValue = (value: string) => {
|
||||
(facet as Record<string, unknown>)[fieldName] = {
|
||||
setFieldValue({
|
||||
'restriction': {
|
||||
'@base': 'xs:string',
|
||||
'length': [{ '@value': value }]
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const setLengthRangeValues = (min: string, max: string) => {
|
||||
@@ -450,13 +438,27 @@
|
||||
if (min !== '') restriction['minLength'] = [{ '@value': min }];
|
||||
if (max !== '') restriction['maxLength'] = [{ '@value': max }];
|
||||
|
||||
(facet as Record<string, unknown>)[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());
|
||||
|
||||
$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) {
|
||||
@@ -583,6 +585,7 @@
|
||||
<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 for={`${baseId}-range-min`}>Min</label>
|
||||
@@ -590,13 +593,13 @@
|
||||
class="form-input"
|
||||
type="text"
|
||||
id={`${baseId}-range-min`}
|
||||
bind:value={() => getRangeValues().min, (v) => { const range = getRangeValues(); setRangeValues(v, range.max, range.minType, range.maxType); }}
|
||||
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={() => getRangeValues().minType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, v, range.maxType); }}
|
||||
bind:value={() => range.minType, (v) => setRangeValues(range.min, range.max, v, range.maxType)}
|
||||
>
|
||||
<option value="Inclusive">Inclusive</option>
|
||||
<option value="Exclusive">Exclusive</option>
|
||||
@@ -608,13 +611,13 @@
|
||||
class="form-input"
|
||||
type="text"
|
||||
id={`${baseId}-range-max`}
|
||||
bind:value={() => getRangeValues().max, (v) => { const range = getRangeValues(); setRangeValues(range.min, v, range.minType, range.maxType); }}
|
||||
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={() => getRangeValues().maxType, (v) => { const range = getRangeValues(); setRangeValues(range.min, range.max, range.minType, v); }}
|
||||
bind:value={() => range.maxType, (v) => setRangeValues(range.min, range.max, range.minType, v)}
|
||||
>
|
||||
<option value="Inclusive">Inclusive</option>
|
||||
<option value="Exclusive">Exclusive</option>
|
||||
@@ -626,14 +629,15 @@
|
||||
<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 for={`${baseId}-length-min`}>Min Length</label>
|
||||
<input class="form-input" id={`${baseId}-length-min`} type="number" bind:value={() => getLengthRangeValues().min, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(v, range.max); }} placeholder="0">
|
||||
<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 for={`${baseId}-length-max`}>Max Length</label>
|
||||
<input class="form-input" id={`${baseId}-length-max`} type="number" bind:value={() => getLengthRangeValues().max, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(range.min, v); }} placeholder="0">
|
||||
<input class="form-input" id={`${baseId}-length-max`} type="number" bind:value={() => lengthRange.max, (v) => setLengthRangeValues(lengthRange.min, v)} placeholder="0">
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user