/** @jsx jsx */ /** @jsxFrag React.Fragment */ import update from 'immutability-helper'; import React from 'react'; import { jsx, css } from '@emotion/react'; import { Drawer, Button, ButtonGroup, HTMLSelect, NumericInput, InputGroup } from '@blueprintjs/core'; import type { Payload, Citation, ItemClassConfiguration, InternalItemReference, ItemListView } from '@riboseinc/paneron-registry-kit/types'; import { useSingleRegisterItemData, PropertyDetailView } from '@riboseinc/paneron-registry-kit'; import { type Extent, CombinedExtentWidget, DEFAULT_EXTENT, } from './extent'; import { type CommonGRItemData, COMMON_PROPERTIES, DEFAULTS as SHARED_DEFAULTS, EditView as CommonEditView, ListItemView as CommonListItemView, InformationSourceEdit, getInformationSourceStub, RelatedItem, ItemTable, type ColumnInfo, type Accuracy, AccuracyEdit, ACCURACY_STUB, } from './common'; export const ParameterValueType = { FILE: 'parameter file name', INTEGER_VALUE: 'integer value', MEASURE: 'measure (w/ UoM)', } as const; const parameterValueTypes = [ ParameterValueType.FILE, ParameterValueType.INTEGER_VALUE, ParameterValueType.MEASURE, ] as const; export interface TransformationParameter { parameter: string // Coordinate operation parameter UUID unitOfMeasurement: string | null // Unit of measurement UUID //name: string // Dependent on type? filename? TODO: Doesn’t exist? type: typeof parameterValueTypes[number] value: string | number | null fileCitation: null | Citation } function getParameterStub(): TransformationParameter { return { parameter: '', unitOfMeasurement: null, //name: '', type: parameterValueTypes[0], value: null, fileCitation: null, }; } export interface TransformationData extends CommonGRItemData { extent: Extent /** UUID of relevant Extent item. */ extentRef?: string scope: string operationVersion: string accuracy: Accuracy parameters: Readonly sourceCRS?: InternalItemReference targetCRS?: InternalItemReference coordOperationMethod?: string } export const transformation: ItemClassConfiguration = { ...COMMON_PROPERTIES, meta: { title: "Transformation", description: "Coordinate Operations — Transformation", id: 'coordinate-ops--transformation', alternativeNames: [] as const, }, defaults: { ...SHARED_DEFAULTS, extent: DEFAULT_EXTENT, scope: '', operationVersion: '', parameters: [], accuracy: ACCURACY_STUB, }, views: { listItemView: CommonListItemView as ItemListView, editView: ({ itemData, onChange, ...props }) => { // NOTE: Yes, Conversion has similar logic, but refactoring this for DRY // is ill-advised at least until Transformation and Conversion themselves // are refactored using a single common ancestor class (SingleOperation or the like), // but ideally until users have the ability to specify this logic themselves. // Refactoring it here const coordMethodParamUUIDs: string[] = (useSingleRegisterItemData( itemData.coordOperationMethod ? { classID: 'coordinate-op-method', itemID: itemData.coordOperationMethod } : null // Cast to Payload is necessary due to RegistryKit wrongly typing useSingleRegisterItemData value ).value as Payload)?.parameters ?? []; const itemParamParamUUIDs = (itemData.parameters ?? []).map(param => param.parameter); const missingParameters = React.useMemo(() => ( coordMethodParamUUIDs.filter(uuid => itemParamParamUUIDs.indexOf(uuid) < 0) ), [itemParamParamUUIDs.toString(), coordMethodParamUUIDs.toString()]); const createParameterValueStub: () => TransformationParameter = React.useCallback(() => { return { ...getParameterStub(), parameter: missingParameters[0] ?? '', }; }, [missingParameters[0]]); const createStubsForMissingOperationMethodParameters = React.useMemo(() => onChange && missingParameters.length > 0 ? function () { const paramValueStubs = missingParameters.map(paramUUID => ({ ...getParameterStub(), parameter: paramUUID, })); onChange(update(itemData, { parameters: { $splice: [[0, 0, ...paramValueStubs]] } })); } : null, [onChange, missingParameters]); const [paramWithOpenCitation, setParamWithOpenCitation] = React.useState(null); const fileCitationBeingEdited = paramWithOpenCitation !== null ? itemData.parameters[paramWithOpenCitation].fileCitation ?? null : null; return ( <> setParamWithOpenCitation(null), [])}> {fileCitationBeingEdited !== null ? onChange!(update(itemData, { parameters: { [paramWithOpenCitation as number]: { fileCitation: { $set: citation } } } } )) : undefined} /> : null} { if (!onChange) { return; } onChange({ ...itemData, ...newData }); } : undefined}> onChange!(update(itemData, { $unset: ['sourceCRS'] })))} onSet={onChange ? ((spec) => onChange!(update(itemData, { sourceCRS: spec }))) : undefined} classIDs={['crs--vertical', 'crs--geodetic']} /> onChange!(update(itemData, { $unset: ['targetCRS'] })))} onSet={onChange ? ((spec) => onChange!(update(itemData, { targetCRS: spec }))) : undefined} classIDs={['crs--vertical', 'crs--geodetic']} /> Create stubs for {missingParameters.length} missing parameter values : undefined} title="Coordinate operation method"> onChange!(update(itemData, { $unset: ['coordOperationMethod'] })))} onSet={onChange ? ((spec) => onChange!(update(itemData, { coordOperationMethod: spec }))) : undefined} classIDs={['coordinate-op-method']} /> onChange?.({ ...itemData, scope: evt.currentTarget.value })} /> onChange!(update(itemData, { extentRef: { $set: ref } })) : undefined} /> For example, GA v2 : undefined}> ) => { onChange ? onChange(update(itemData, { operationVersion: { $set: evt.currentTarget.value } })) : void 0; }} /> onChange(update(itemData, { accuracy: { $set: accuracy } } ))) : undefined} /> onChange!(update(itemData, { parameters: spec })) : undefined} placeholderItem={createParameterValueStub} columnInfo={React.useMemo(() => ({ parameter: { title: "Coordinate operation parameter", width: 320, CellRenderer: function renderTransformationParameterParameter ({ val, onChange }) { return onChange!({ $set: '' }) : undefined} onSet={onChange} /> } }, type: { title: "Value Type", width: 160, CellRenderer: function renderTransformationParameterValueType ({ val, onChange }) { return (onChange ? ({ value: param, label: param }))} disabled={!onChange} onChange={(evt) => onChange!({ $set: evt.currentTarget.value as typeof parameterValueTypes[number] }) } /> : ); }, }, value: { title: "Value", width: 128, CellRenderer: function renderTransformationParameterValue ({ val, onChange, item }) { switch (item.type) { case ParameterValueType.INTEGER_VALUE: return onChange!({ $set: val })} />; default: return ) => onChange!({ $set: evt.currentTarget.value })} />; } }, }, unitOfMeasurement: { title: "Unit of Measurement", width: 256, CellRenderer: function renderTransformationParameterUoM ({ val, onChange, item }) { return (val || item.type === ParameterValueType.MEASURE ? onChange!({ $set: '' }) : undefined} onSet={onChange} /> : null ); }, }, fileCitation: { title: "Information Source", width: 192, CellRenderer: function renderTransformationParameterFileCitation ({ val, onChange, itemIndex }) { const hasVal = val !== null && val !== undefined; return <> {val && onChange ? ; }, }, }), []) as ColumnInfo} /> ) }, }, validatePayload: async () => true, sanitizePayload: async (t) => t, } as const;