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 { error, success } from "$src/modules/utils/toast.svelte";
import * as Tooltip from "$src/lib/components/ui/tooltip"; import * as Tooltip from "$src/lib/components/ui/tooltip";
import type { AuditReport, AuditReportData } from "$src/types/report"; import type { 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( let activeDocument = $derived(
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
@@ -122,6 +122,31 @@
return spec.requirements[reqIndex]; 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) { function toggleRequirementDetails(specIndex: number, reqIndex: number) {
const key = `${specIndex}-${reqIndex}`; const key = `${specIndex}-${reqIndex}`;
if (expandedRequirements.has(key)) { if (expandedRequirements.has(key)) {
@@ -230,6 +255,7 @@
</div> </div>
{#each activeDocument.specifications.specification as spec, index} {#each activeDocument.specifications.specification as spec, index}
{@const usage = getDocumentSpecificationUsage(spec)} {@const usage = getDocumentSpecificationUsage(spec)}
{@const requirementGroups = getRequirementGroups(spec)}
<div class="specification-card {auditReport ? 'with-audit' : ''} {auditReport && getSpecificationStatus(index, auditReport.data) !== null ? (getSpecificationStatus(index, auditReport.data) === 'skipped' ? 'spec-skipped' : (getSpecificationStatus(index, auditReport.data) ? 'spec-pass' : 'spec-fail')) : ''}"> <div class="specification-card {auditReport ? 'with-audit' : ''} {auditReport && getSpecificationStatus(index, auditReport.data) !== null ? (getSpecificationStatus(index, auditReport.data) === 'skipped' ? 'spec-skipped' : (getSpecificationStatus(index, auditReport.data) ? 'spec-pass' : 'spec-fail')) : ''}">
<div <div
class="spec-card-header" class="spec-card-header"
@@ -428,37 +454,36 @@
</div> </div>
<!-- Requirements Section --> <!-- Requirements Section -->
{#if Array.isArray(spec.requirements) && spec.requirements.length > 0} {#if requirementGroups.length > 0}
<div class="facet-section"> <div class="facet-section">
<h3>Requirements</h3> <h3>Requirements</h3>
<div class="facets-list"> <div class="facets-list">
{#each Object.entries(spec.requirements || {}) as [facetType, facets]} {#each requirementGroups as group}
{#if Array.isArray(facets) && facets.length > 0} <div class="facet-group">
<div class="facet-group"> {#each group.items as item}
{#each facets as facet, facetIndex} {@const reqAuditData = auditReport ? getRequirementStatus(index, item.reqIndex, auditReport.data) : null}
{@const reqAuditData = auditReport ? getRequirementStatus(index, facetIndex, auditReport.data) : null} {@const specStatus = auditReport ? getSpecificationStatus(index, auditReport.data) : null}
{@const specStatus = auditReport ? getSpecificationStatus(index, auditReport.data) : null} <div class="facet-item {auditReport && reqAuditData && specStatus !== 'skipped' ? (reqAuditData.status ? 'audit-pass' : 'audit-fail') : ''}">
<div class="facet-item {auditReport && reqAuditData && specStatus !== 'skipped' ? (reqAuditData.status ? 'audit-pass' : 'audit-fail') : ''}"> <button class="facet-header" onclick={() => {if (auditReport && reqAuditData && specStatus !== 'skipped') toggleRequirementDetails(index, item.reqIndex)}}>
<button class="facet-header" onclick={() => {if (auditReport && reqAuditData && specStatus !== 'skipped') toggleRequirementDetails(index, facetIndex)}}> <span class="facet-bullet"></span>
<span class="facet-bullet"></span> <span class="facet-text">{@html IDS.stringifyFacet("requirements", item.facet, group.facetType, spec)}</span>
<span class="facet-text">{@html IDS.stringifyFacet("requirements", facet, facetType, spec)}</span> {#if auditReport && reqAuditData && specStatus !== 'skipped'}
{#if auditReport && reqAuditData && specStatus !== 'skipped'} {#if reqAuditData.total_applicable > 0}
{#if reqAuditData.total_applicable > 0} <div class="audit-details-toggle">
<div class="audit-details-toggle"> {reqAuditData.status ? 'PASS' : 'FAIL'} ({reqAuditData.total_pass}/{reqAuditData.total_applicable})
{reqAuditData.status ? 'PASS' : 'FAIL'} ({reqAuditData.total_pass}/{reqAuditData.total_applicable}) <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:rotated={isRequirementDetailsExpanded(index, item.reqIndex)}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:rotated={isRequirementDetailsExpanded(index, facetIndex)}> <polyline points="6,9 12,15 18,9"></polyline>
<polyline points="6,9 12,15 18,9"></polyline> </svg>
</svg> </div>
</div> {:else}
{:else} <span class="audit-status-badge">
<span class="audit-status-badge"> {reqAuditData.status ? 'PASS' : 'FAIL'}
{reqAuditData.status ? 'PASS' : 'FAIL'} </span>
</span>
{/if}
{/if} {/if}
</button> {/if}
{#if reqAuditData && isRequirementDetailsExpanded(index, facetIndex)} </button>
{#if reqAuditData && isRequirementDetailsExpanded(index, item.reqIndex)}
<div class="facet-expansion"> <div class="facet-expansion">
<div class="entity-tables"> <div class="entity-tables">
{#if reqAuditData.passed_entities && reqAuditData.passed_entities.length > 0} {#if reqAuditData.passed_entities && reqAuditData.passed_entities.length > 0}
@@ -623,11 +648,10 @@
{/if} {/if}
</div> </div>
</div> </div>
{/if} {/if}
</div> </div>
{/each} {/each}
</div> </div>
{/if}
{/each} {/each}
</div> </div>
</div> </div>
@@ -48,6 +48,27 @@
const uniqueId = Math.random().toString(36).slice(2, 8); const uniqueId = Math.random().toString(36).slice(2, 8);
let baseId = $derived(`restriction-${fieldName}-${uniqueId}`); let baseId = $derived(`restriction-${fieldName}-${uniqueId}`);
const activeDocument = $derived(
IDS.Module.activeDocument ? (IDS.Module.documents[IDS.Module.activeDocument] as IdsDocument) : null
);
const documentState = $derived(
IDS.Module.activeDocument ? (IDS.Module.states[IDS.Module.activeDocument] as DocumentState) : null
);
const activeSpecification = $derived(
activeDocument && documentState && documentState.activeSpecification !== null && activeDocument.specifications?.specification
? (activeDocument.specifications.specification[documentState.activeSpecification] as Specification)
: null
);
const getFieldValue = () => (facet as Record<string, unknown>)[fieldName] as FacetValue | string | undefined;
const setFieldValue = (value: FacetValue | string) => {
(facet as Record<string, unknown>)[fieldName] = value;
};
const getFacetValue = () => {
const value = getFieldValue();
return typeof value === 'object' && value !== null ? (value as FacetValue) : undefined;
};
// Predefined Types autocompletions // Predefined Types autocompletions
let predefinedTypeOptions: string[] = $state([]); let predefinedTypeOptions: string[] = $state([]);
@@ -60,17 +81,6 @@
// Get the active specification's IFC schemas // Get the active specification's IFC schemas
const getIfcVersions = () => { const getIfcVersions = () => {
const activeDocument = IDS.Module.activeDocument
? (IDS.Module.documents[IDS.Module.activeDocument] 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']; const versions = activeSpecification?.["@ifcVersion"] || ['IFC4'];
// TODO: Fix: Filter out IFC4X3 because it's buggy // TODO: Fix: Filter out IFC4X3 because it's buggy
return versions.filter(version => version !== 'IFC4X3_ADD2'); return versions.filter(version => version !== 'IFC4X3_ADD2');
@@ -86,17 +96,6 @@
// Get all entity names from Entity facets in Applicability // Get all entity names from Entity facets in Applicability
const getApplicabilityEntityNames = () => { const getApplicabilityEntityNames = () => {
const activeDocument = IDS.Module.activeDocument
? (IDS.Module.documents[IDS.Module.activeDocument] 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 []; if (!activeSpecification?.applicability?.entity) return [];
const entityFacets = activeSpecification.applicability.entity as Facet[]; const entityFacets = activeSpecification.applicability.entity as Facet[];
@@ -122,17 +121,6 @@
// Get entity facets with their predefined types // Get entity facets with their predefined types
const getApplicabilityEntityFacets = () => { const getApplicabilityEntityFacets = () => {
const activeDocument = IDS.Module.activeDocument
? (IDS.Module.documents[IDS.Module.activeDocument] 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 []; if (!activeSpecification?.applicability?.entity) return [];
return (activeSpecification.applicability.entity as Facet[]).map(entityFacet => { return (activeSpecification.applicability.entity as Facet[]).map(entityFacet => {
@@ -295,7 +283,7 @@
}; };
const getRestrictionType = () => { const getRestrictionType = () => {
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined; const fieldValue = getFacetValue();
if (!fieldValue) return 'Simple'; if (!fieldValue) return 'Simple';
if (fieldValue.simpleValue !== undefined) return 'Simple'; if (fieldValue.simpleValue !== undefined) return 'Simple';
if (fieldValue['restriction']) { if (fieldValue['restriction']) {
@@ -311,7 +299,7 @@
}; };
const getSimpleValue = (): string => { 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 // For special properties (eg. @dataType), we return the value directly
if (typeof fieldValue === 'string') return fieldValue; if (typeof fieldValue === 'string') return fieldValue;
@@ -323,7 +311,7 @@
}; };
const getEnumerationValues = () => { const getEnumerationValues = () => {
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined; const fieldValue = getFacetValue();
if (!fieldValue?.['restriction']) return ['']; if (!fieldValue?.['restriction']) return [''];
const restriction = fieldValue['restriction'] as Restriction; const restriction = fieldValue['restriction'] as Restriction;
const enumValues = restriction['enumeration'] as RestrictionValue[] | undefined; const enumValues = restriction['enumeration'] as RestrictionValue[] | undefined;
@@ -332,7 +320,7 @@
}; };
const getPatternValue = () => { const getPatternValue = () => {
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined; const fieldValue = getFacetValue();
if (!fieldValue?.['restriction']) return ''; if (!fieldValue?.['restriction']) return '';
const restriction = fieldValue['restriction'] as Restriction; const restriction = fieldValue['restriction'] as Restriction;
const pattern = restriction['pattern'] as RestrictionValue[] | undefined; const pattern = restriction['pattern'] as RestrictionValue[] | undefined;
@@ -341,7 +329,7 @@
}; };
const getRangeValues = () => { 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' }; if (!fieldValue?.['restriction']) return { min: '', max: '', minType: 'Inclusive', maxType: 'Inclusive' };
const restriction = fieldValue['restriction'] as Restriction; const restriction = fieldValue['restriction'] as Restriction;
@@ -367,7 +355,7 @@
}; };
const getLengthValue = () => { const getLengthValue = () => {
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined; const fieldValue = getFacetValue();
if (!fieldValue?.['restriction']) return ''; if (!fieldValue?.['restriction']) return '';
const restriction = fieldValue['restriction'] as Restriction; const restriction = fieldValue['restriction'] as Restriction;
const length = restriction['length'] as RestrictionValue[] | undefined; const length = restriction['length'] as RestrictionValue[] | undefined;
@@ -376,7 +364,7 @@
}; };
const getLengthRangeValues = () => { const getLengthRangeValues = () => {
const fieldValue = (facet as Record<string, unknown>)[fieldName] as FacetValue | undefined; const fieldValue = getFacetValue();
if (!fieldValue?.['restriction']) return { min: '', max: '' }; if (!fieldValue?.['restriction']) return { min: '', max: '' };
const restriction = fieldValue['restriction'] as Restriction; const restriction = fieldValue['restriction'] as Restriction;
@@ -395,29 +383,29 @@
const setSimpleValue = (value: string) => { const setSimpleValue = (value: string) => {
// For special properties (eg. @dataType), we set the value directly // For special properties (eg. @dataType), we set the value directly
if (isSpecialProp) { if (isSpecialProp) {
(facet as Record<string, unknown>)[fieldName] = value; setFieldValue(value);
return; return;
} }
(facet as Record<string, unknown>)[fieldName] = { simpleValue: value }; setFieldValue({ simpleValue: value });
}; };
const setEnumerationValues = (values: string[]) => { const setEnumerationValues = (values: string[]) => {
const enumItems = values.filter(v => v && typeof v === 'string' && v.trim() !== '').map(v => ({ '@value': v })); const enumItems = values.filter(v => v && typeof v === 'string' && v.trim() !== '').map(v => ({ '@value': v }));
(facet as Record<string, unknown>)[fieldName] = { setFieldValue({
'restriction': { 'restriction': {
'@base': 'xs:string', '@base': 'xs:string',
'enumeration': enumItems 'enumeration': enumItems
} }
}; });
}; };
const setPatternValue = (value: string) => { const setPatternValue = (value: string) => {
(facet as Record<string, unknown>)[fieldName] = { setFieldValue({
'restriction': { 'restriction': {
'@base': 'xs:string', '@base': 'xs:string',
'pattern': [{ '@value': value }] 'pattern': [{ '@value': value }]
} }
}; });
}; };
const setRangeValues = (min: string, max: string, minType: string, maxType: string) => { const setRangeValues = (min: string, max: string, minType: string, maxType: string) => {
@@ -432,16 +420,16 @@
(restriction as Record<string, RestrictionValue[]>)[maxKey] = [{ '@value': max }]; (restriction as Record<string, RestrictionValue[]>)[maxKey] = [{ '@value': max }];
} }
(facet as Record<string, unknown>)[fieldName] = { 'restriction': restriction }; setFieldValue({ 'restriction': restriction });
}; };
const setLengthValue = (value: string) => { const setLengthValue = (value: string) => {
(facet as Record<string, unknown>)[fieldName] = { setFieldValue({
'restriction': { 'restriction': {
'@base': 'xs:string', '@base': 'xs:string',
'length': [{ '@value': value }] 'length': [{ '@value': value }]
} }
}; });
}; };
const setLengthRangeValues = (min: string, max: string) => { const setLengthRangeValues = (min: string, max: string) => {
@@ -450,13 +438,27 @@
if (min !== '') restriction['minLength'] = [{ '@value': min }]; if (min !== '') restriction['minLength'] = [{ '@value': min }];
if (max !== '') restriction['maxLength'] = [{ '@value': max }]; if (max !== '') restriction['maxLength'] = [{ '@value': max }];
(facet 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()); 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) => { const handleTypeChange = (newType: string) => {
hasUserSelectedType = true;
restrictionType = newType; restrictionType = newType;
switch (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`}> <input class="form-input" type="text" bind:value={() => getPatternValue(), (v) => setPatternValue(v)} placeholder="Enter regex pattern (e.g., DT[0-9]{2})" aria-label={`${label} pattern`}>
{:else if restrictionType === 'Range'} {:else if restrictionType === 'Range'}
{@const range = getRangeValues()}
<div class="range-controls"> <div class="range-controls">
<div class="range-group"> <div class="range-group">
<label for={`${baseId}-range-min`}>Min</label> <label for={`${baseId}-range-min`}>Min</label>
@@ -590,13 +593,13 @@
class="form-input" class="form-input"
type="text" type="text"
id={`${baseId}-range-min`} 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" placeholder="0"
> >
<select <select
class="form-input" class="form-input"
aria-label="Min bound type" 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="Inclusive">Inclusive</option>
<option value="Exclusive">Exclusive</option> <option value="Exclusive">Exclusive</option>
@@ -608,13 +611,13 @@
class="form-input" class="form-input"
type="text" type="text"
id={`${baseId}-range-max`} 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" placeholder="0"
> >
<select <select
class="form-input" class="form-input"
aria-label="Max bound type" 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="Inclusive">Inclusive</option>
<option value="Exclusive">Exclusive</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`}> <input class="form-input" type="number" bind:value={() => getLengthValue(), (v) => setLengthValue(v)} placeholder="Enter exact length" aria-label={`${label} length`}>
{:else if restrictionType === 'Length Range'} {:else if restrictionType === 'Length Range'}
{@const lengthRange = getLengthRangeValues()}
<div class="length-range-controls"> <div class="length-range-controls">
<div class="length-group"> <div class="length-group">
<label for={`${baseId}-length-min`}>Min Length</label> <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>
<div class="length-group"> <div class="length-group">
<label for={`${baseId}-length-max`}>Max Length</label> <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>
</div> </div>
{/if} {/if}