fixes and cleanups from old branch

This commit is contained in:
Sayan Jyoti Das
2026-03-18 11:22:41 +05:30
parent 4dc6a0f2bc
commit 03de69814a
2 changed files with 117 additions and 89 deletions
@@ -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}