From c9b518daa934d9b756a3b9cc25f331bb0b397565 Mon Sep 17 00:00:00 2001 From: Einar Date: Thu, 12 Mar 2026 21:31:42 +0100 Subject: [PATCH 1/4] Adding missing client fildering and on refresh callback --- Source/DataPage/DataPage.tsx | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Source/DataPage/DataPage.tsx b/Source/DataPage/DataPage.tsx index 0ddb8d6..999b5ce 100644 --- a/Source/DataPage/DataPage.tsx +++ b/Source/DataPage/DataPage.tsx @@ -66,13 +66,21 @@ export const Columns = ({ children }: ColumnProps) => { if (context.query.prototype instanceof QueryFor) { return ( - + {children} ); } else { return ( - + {children} ); } @@ -80,7 +88,7 @@ export const Columns = ({ children }: ColumnProps) => { export interface IDetailsComponentProps { item: TDataType; - + onRefresh?: () => void; } interface IDataPageContext extends DataPageProps { @@ -148,6 +156,16 @@ export interface DataPageProps | IObservable * Default filters to use */ defaultFilters?: DataTableFilterMeta; + + /** + * When true, filtering is performed client-side only + */ + clientFiltering?: boolean; + + /** + * Callback triggered to signal data refresh + */ + onRefresh?(): void; } /** @@ -176,7 +194,7 @@ const DataPage = | IObservableQueryFor {props.detailsComponent && selectedItem && - + } From 8cdb9140c56efcf254cf0f93aa83f1cfbe20420c Mon Sep 17 00:00:00 2001 From: Einar Date: Thu, 12 Mar 2026 21:31:54 +0100 Subject: [PATCH 2/4] Adding missing module for types --- Source/index.ts | 2 ++ Source/package.json | 5 +++++ Source/types/index.ts | 5 +++++ 3 files changed, 12 insertions(+) create mode 100644 Source/types/index.ts diff --git a/Source/index.ts b/Source/index.ts index b682e5c..06620c6 100644 --- a/Source/index.ts +++ b/Source/index.ts @@ -14,6 +14,7 @@ import * as PivotViewer from './PivotViewer'; import * as SchemaEditor from './SchemaEditor'; import * as TimeMachine from './TimeMachine'; import * as Toolbar from './Toolbar'; +import * as Types from './types'; export { CommandDialog, @@ -29,5 +30,6 @@ export { SchemaEditor, TimeMachine, Toolbar, + Types, }; diff --git a/Source/package.json b/Source/package.json index 463b366..50e118d 100644 --- a/Source/package.json +++ b/Source/package.json @@ -84,6 +84,11 @@ "require": "./dist/cjs/Toolbar/index.js", "import": "./dist/esm/Toolbar/index.js" }, + "./types": { + "types": "./dist/esm/types/index.d.ts", + "require": "./dist/cjs/types/index.js", + "import": "./dist/esm/types/index.js" + }, "./styles": "./dist/esm/tailwind-utilities.css" }, "scripts": { diff --git a/Source/types/index.ts b/Source/types/index.ts new file mode 100644 index 0000000..04f79a1 --- /dev/null +++ b/Source/types/index.ts @@ -0,0 +1,5 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +export * from './JsonSchema'; +export * from './TypeFormat'; From be613b37893ebf7951a3e7ee3ca2ea16e27b981f Mon Sep 17 00:00:00 2001 From: Einar Date: Thu, 12 Mar 2026 21:32:12 +0100 Subject: [PATCH 3/4] Adding missing editor mode --- .../ObjectContentEditor.tsx | 249 ++++++++++++++++-- 1 file changed, 231 insertions(+), 18 deletions(-) diff --git a/Source/ObjectContentEditor/ObjectContentEditor.tsx b/Source/ObjectContentEditor/ObjectContentEditor.tsx index fda5b4d..901dd2d 100644 --- a/Source/ObjectContentEditor/ObjectContentEditor.tsx +++ b/Source/ObjectContentEditor/ObjectContentEditor.tsx @@ -2,20 +2,92 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. import { Tooltip } from 'primereact/tooltip'; -import React, { useState, useCallback, useMemo } from 'react'; +import React, { useState, useCallback, useMemo, useEffect } from 'react'; import * as faIcons from 'react-icons/fa6'; import { ObjectNavigationalBar } from '../ObjectNavigationalBar'; import { Json, JsonSchema, JsonSchemaProperty } from '../types/JsonSchema'; import { getValueAtPath } from './objectHelpers'; +import { InputText } from 'primereact/inputtext'; +import { InputNumber } from 'primereact/inputnumber'; +import { Checkbox } from 'primereact/checkbox'; +import { Calendar } from 'primereact/calendar'; +import { InputTextarea } from 'primereact/inputtextarea'; export interface ObjectContentEditorProps { object: Json; timestamp?: Date; schema: JsonSchema; + /** + * When true, renders editable input fields for each property respecting type/format + */ + editMode?: boolean; + /** + * Called with the updated object after any field edit + */ + onChange?: (object: Json) => void; + /** + * Called when the validation state changes + */ + onValidationChange?: (hasErrors: boolean) => void; } -export const ObjectContentEditor = ({ object, timestamp, schema }: ObjectContentEditorProps) => { +export const ObjectContentEditor = ({ object, timestamp, schema, editMode = false, onChange, onValidationChange }: ObjectContentEditorProps) => { const [navigationPath, setNavigationPath] = useState([]); + const [validationErrors, setValidationErrors] = useState>({}); + + const validateValue = useCallback((propertyName: string, value: Json, property: JsonSchemaProperty): string | undefined => { + if (editMode) { + if (value === null || value === undefined || value === '') { + return 'This field is required'; + } + } else { + const isRequired = schema.required?.includes(propertyName); + if (isRequired && (value === null || value === undefined || value === '')) { + return 'This field is required'; + } + } + + if (property.type === 'string' && typeof value === 'string') { + if (property.format === 'email' && value && !value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) { + return 'Invalid email format'; + } + if (property.format === 'uri' && value && !value.match(/^https?:\/\/.+/)) { + return 'Invalid URI format'; + } + } + + if (property.type === 'number' || property.type === 'integer') { + if (value !== null && value !== undefined && value !== '' && isNaN(Number(value))) { + return 'Must be a valid number'; + } + } + + return undefined; + }, [schema, editMode]); + + useEffect(() => { + if (!editMode || navigationPath.length > 0) return; + + const errors: Record = {}; + const properties = schema.properties || {}; + + Object.entries(properties).forEach(([propertyName, property]) => { + const value = (object as Record)[propertyName]; + const error = validateValue(propertyName, value, property as JsonSchemaProperty); + if (error) { + errors[propertyName] = error; + } + }); + + setValidationErrors(errors); + }, [object, schema, editMode, navigationPath, validateValue]); + + useEffect(() => { + if (editMode && onValidationChange) { + const hasErrors = Object.keys(validationErrors).length > 0; + onValidationChange(hasErrors); + } + }, [validationErrors, editMode, onValidationChange]); const navigateToProperty = useCallback((key: string) => { setNavigationPath([...navigationPath, key]); @@ -81,6 +153,7 @@ export const ObjectContentEditor = ({ object, timestamp, schema }: ObjectContent textAlign: 'left', fontWeight: 500, width: '140px', + whiteSpace: 'nowrap', }; const valueStyle: React.CSSProperties = { @@ -90,10 +163,141 @@ export const ObjectContentEditor = ({ object, timestamp, schema }: ObjectContent }; const infoIconStyle: React.CSSProperties = { - marginLeft: '6px', - fontSize: '12px', - color: 'rgba(100, 150, 255, 0.6)', - cursor: 'help', + fontSize: '0.875rem', + color: 'var(--text-color-secondary)', + flexShrink: 0, + }; + + const updateValue = useCallback((propertyName: string, newValue: Json) => { + if (!onChange) return; + + const updatedObject = { ...(object as Record) }; + updatedObject[propertyName] = newValue; + onChange(updatedObject); + }, [object, onChange]); + + const renderEditField = (propertyName: string, property: JsonSchemaProperty, value: Json) => { + const error = validationErrors[propertyName]; + + const handleChange = (newValue: Json) => { + updateValue(propertyName, newValue); + const validationError = validateValue(propertyName, newValue, property); + setValidationErrors(prev => { + const newErrors = { ...prev }; + if (validationError) { + newErrors[propertyName] = validationError; + } else { + delete newErrors[propertyName]; + } + return newErrors; + }); + }; + + const inputStyle = { + width: '100%', + ...(error ? { borderColor: 'var(--red-500)' } : {}) + }; + + if (property.type === 'boolean') { + return ( +
+ handleChange(e.checked ?? false)} + /> + {error && {error}} +
+ ); + } + + if (property.type === 'number' || property.type === 'integer') { + return ( +
+ handleChange(e.value ?? null)} + mode="decimal" + useGrouping={false} + style={inputStyle} + /> + {error && {error}} +
+ ); + } + + if (property.type === 'string' && property.format === 'date-time') { + const dateValue = value ? new Date(value as string) : null; + return ( +
+ handleChange(e.value instanceof Date ? e.value.toISOString() : null)} + showTime + showIcon + style={inputStyle} + /> + {error && {error}} +
+ ); + } + + if (property.type === 'string' && property.format === 'date') { + const dateValue = value ? new Date(value as string) : null; + return ( +
+ handleChange(e.value instanceof Date ? e.value.toISOString().split('T')[0] : null)} + showIcon + style={inputStyle} + /> + {error && {error}} +
+ ); + } + + if (property.type === 'array') { + return ( +
+ Array editing not yet supported +
+ ); + } + + if (property.type === 'object') { + return ( +
+ Object editing not yet supported +
+ ); + } + + const isLongText = (value as string)?.length > 50; + + if (isLongText) { + return ( +
+ handleChange(e.target.value)} + rows={3} + style={inputStyle} + /> + {error && {error}} +
+ ); + } + + return ( +
+ handleChange(e.target.value)} + style={inputStyle} + /> + {error && {error}} +
+ ); }; const renderValue = (value: Json, propertyName: string) => { @@ -184,22 +388,31 @@ export const ObjectContentEditor = ({ object, timestamp, schema }: ObjectContent const value = (currentData as Record)[propertyName]; const isSchemaProperty = navigationPath.length === 0; - const description = isSchemaProperty && typeof propertyDef === 'object' && propertyDef !== null && 'description' in propertyDef - ? (propertyDef as JsonSchemaProperty).description - : undefined; + const property = isSchemaProperty && typeof propertyDef === 'object' && propertyDef !== null && 'type' in propertyDef + ? (propertyDef as JsonSchemaProperty) + : null; + const description = property?.description; return ( - {propertyName} - {description && ( - - )} + + {propertyName} + {description && ( + + )} + + + + {editMode && property + ? renderEditField(propertyName, property, value) + : renderValue(value as Json, propertyName) + } - {renderValue(value as Json, propertyName)} ); })} @@ -210,7 +423,7 @@ export const ObjectContentEditor = ({ object, timestamp, schema }: ObjectContent return (
- + Date: Fri, 13 Mar 2026 05:45:41 +0100 Subject: [PATCH 4/4] Fixing setup for tests - removing sinon chai, since this is never used --- Source/vitest.setup.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/vitest.setup.ts b/Source/vitest.setup.ts index 9853f52..26bc6ba 100644 --- a/Source/vitest.setup.ts +++ b/Source/vitest.setup.ts @@ -5,6 +5,4 @@ import 'reflect-metadata'; import * as chai from 'chai'; chai.should(); import * as chaiAsPromised from 'chai-as-promised'; -import * as sinonChai from 'sinon-chai'; -chai.use(sinonChai.default); chai.use(chaiAsPromised.default);