Skip to content

Commit

Permalink
session editing improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
heswell committed May 11, 2024
1 parent 7cff2bd commit 907b335
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 148 deletions.
3 changes: 1 addition & 2 deletions vuu-ui/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
"additionalHooks": "(useLayoutEffectSkipFirst)"
}
],

"no-unused-vars": 0,
"no-prototype-builtins": 0,
"prefer-const": ["error", { "destructuring": "all" }],
"@typescript-eslint/no-unused-vars": [
"error",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ const toDataSourceRow =
return [index, index, true, false, 1, 0, String(data[key]), 0, ...data];
};

const isError = (err: unknown): err is { message: string } =>

Check failure on line 77 in vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts

View workflow job for this annotation

GitHub Actions / lint-and-typecheck

'isError' is assigned a value but never used. Allowed unused vars must match /^_$/u
typeof err === "object" && err !== null && err.hasOwnProperty("message");

const buildTableSchema = (
columns: ColumnDescriptor[],
keyColumn?: string
Expand Down Expand Up @@ -476,15 +479,47 @@ export class ArrayDataSource
}
};

protected update = (row: VuuRowDataItemType[], columnName: string) => {
private validateDataValue(columnName: string, value: VuuRowDataItemType) {
console.log(`validate data value ${columnName} ${value}`);
const columnDescriptor = this.columnDescriptors.find(
(col) => col.name === columnName
);
if (columnDescriptor) {
switch (columnDescriptor.serverDataType) {
case "int":
{
if (typeof value === "number") {
if (Math.floor(value) !== value) {
throw Error(`${columnName} is int but value = ${value}`);
}
} else if (typeof value === "string") {
const numericValue = parseFloat(value);
if (Math.floor(numericValue) !== numericValue) {
throw Error(`${columnName} is ${value} is not a valid integer`);
}
}
}
break;
default:
}
} else {
throw Error(`Unknown column ${columnName}`);
}
}

protected updateDataItem = (
keyValue: string,
columnName: string,
value: VuuRowDataItemType
) => {
this.validateDataValue(columnName, value);
// TODO take sorting, filtering. grouping into account
const keyValue = row[this.key];
const colIndex = this.#columnMap[columnName];
const dataColIndex = this.dataMap?.[columnName];
const dataIndex = this.#data.findIndex((row) => row[KEY] === keyValue);
if (dataIndex !== -1 && dataColIndex !== undefined) {
const dataSourceRow = this.#data[dataIndex];
dataSourceRow[colIndex] = row[dataColIndex];
dataSourceRow[colIndex] = value;
const { from, to } = this.#range;
const [rowIdx] = dataSourceRow;
if (rowIdx >= from && rowIdx < to) {
Expand All @@ -493,6 +528,13 @@ export class ArrayDataSource
}
};

protected update = (row: VuuRowDataItemType[], columnName: string) => {
// TODO take sorting, filtering. grouping into account
const keyValue = row[this.key] as string;
const dataColIndex = this.dataMap?.[columnName] as number;
return this.updateDataItem(keyValue, columnName, row[dataColIndex]);
};

protected updateRow = (row: VuuRowDataItemType[]) => {
// TODO take sorting, filtering. grouping into account
const keyValue = row[this.key];
Expand Down Expand Up @@ -704,13 +746,13 @@ export class ArrayDataSource
case "VP_EDIT_CELL_RPC":
{
// TODO
// const { rowKey, field, value } = rpcRequest;
// try {
// this.update(rowKey, field, value);
// resolve(undefined);
// } catch (error) {
// resolve({ error: String(error), type: "VP_EDIT_RPC_REJECT" });
// }
const { rowKey, field, value } = rpcRequest;
try {
this.updateDataItem(rowKey, field, value);
resolve(undefined);
} catch (error) {
resolve({ error: String(error), type: "VP_EDIT_RPC_REJECT" });
}
}

break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
flex-direction: column;
gap: 3px;
min-width: 400px;
padding: 6px;
}

.vuuSessionEditingForm-content {
Expand All @@ -12,21 +11,9 @@
flex: 1 1 auto;
gap: 3px;
overflow: auto;
padding: var(--salt-spacing-200);
}

.vuuSessionEditingForm-field {
align-items: center;
display: flex;
height: 32px;
}

.vuuSessionEditingForm-fieldLabel {
flex: 0 0 50%;
}

.vuuSessionEditingForm-fieldValue {
max-width: 50%;
}

.vuuSessionEditingForm-fieldValue.vuuReadOnly {
font-weight: var(--salt-text-label-fontWeight-strong);
Expand All @@ -35,11 +22,15 @@
.vuuSessionEditingForm-buttonbar {
align-items: center;
border-top: solid 1px var(--salt-container-primary-borderColor);
box-sizing: content-box;
display: flex;
justify-content: flex-end;
flex: 0 0 autox;
gap: 6px;
padding-top: 6px;
height: var(--salt-size-base);
margin: var(--salt-spacing-200);
padding-top: var(--salt-spacing-200);

}

.vuuSessionEditingForm-errorBanner {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,25 @@ import {
hasAction,
isErrorResponse,
isValidNumber,
queryClosest,
shallowEquals,
} from "@finos/vuu-utils";
import { Button, useIdMemo } from "@salt-ds/core";
import {
Button,
FormField,
FormFieldHelperText,
FormFieldLabel,
Input,
useIdMemo,
} from "@salt-ds/core";
import { useComponentCssInjection } from "@salt-ds/styles";
import { useWindow } from "@salt-ds/window";
import cx from "clsx";
import {
ChangeEvent,
ChangeEventHandler,
FocusEvent,
FocusEventHandler,
HTMLAttributes,
useCallback,
useEffect,
Expand Down Expand Up @@ -48,7 +58,7 @@ export type FormConfig = {
export interface SessionEditingFormProps
extends HTMLAttributes<HTMLDivElement> {
config: FormConfig;
onClose: () => void;
onClose?: () => void;
dataSource?: DataSource;
schema?: TableSchema;
}
Expand All @@ -67,17 +77,24 @@ const getField = (
}
};

const getFieldNameAndValue = (
evt: ChangeEvent | FocusEvent
): [string, string] => {
const {
dataset: { field },
value,
} = evt.target as HTMLInputElement;
if (field === undefined) {
throw Error("SessionEditingForm, form field has no field name");
const getFieldNameAndValue = ({
target,
}: ChangeEvent<HTMLInputElement> | FocusEvent<HTMLInputElement>): [
string,
string
] => {
const formField = queryClosest(target, ".saltFormField");
if (formField) {
const {
dataset: { field },
} = formField;
if (field === undefined) {
throw Error("SessionEditingForm, form field has no field data attribute");
}
return [field, target.value];
} else {
throw Error("Form control is not enclosed in FormField");
}
return [field, value];
};

const Status = {
Expand Down Expand Up @@ -167,17 +184,24 @@ export const SessionEditingForm = ({
window: targetWindow,
});

const [fieldStatusValues, setFieldStatusValues] = useState<
Record<string, string | undefined>
>({});
const [values, setValues] = useState<FormValues>();
const [errorMessage, setErrorMessage] = useState("");
const formContentRef = useRef<HTMLDivElement>(null);
const initialDataRef = useRef<FormValues>();
const dataStatusRef = useRef(Status.uninitialised);

const ds = getDataSource(dataSourceProp, schema);
const { columns } = ds;
const columnMap = buildColumnMap(ds.columns);

const dataSource = useMemo(() => {
const applyServerData = (data: VuuDataRow) => {
if (columnMap) {
const values: { [key: string]: VuuRowDataItemType } = {};
for (const column of dataSource.columns) {
for (const column of columns) {
values[column] = data[columnMap[column]];
}
if (dataStatusRef.current === Status.uninitialised) {
Expand All @@ -188,8 +212,6 @@ export const SessionEditingForm = ({
}
};

const ds = getDataSource(dataSourceProp, schema);
const columnMap = buildColumnMap(ds.columns);
ds.subscribe({ range: { from: 0, to: 5 } }, (message) => {
if (message.type === "viewport-update" && message.rows) {
if (dataStatusRef.current === Status.uninitialised) {
Expand All @@ -200,15 +222,16 @@ export const SessionEditingForm = ({
}
});
return ds;
}, [dataSourceProp, schema]);
}, [columnMap, columns, ds]);

const id = useIdMemo(idProp);

const handleChange = useCallback(
const handleChange = useCallback<ChangeEventHandler<HTMLInputElement>>(
(evt) => {
const [field, value] = getFieldNameAndValue(evt);
const { type } = getField(fields, field);
const typedValue = getTypedValue(value, type);
// const { type } = getField(fields, field);
// const typedValue = getTypedValue(value, type);
const typedValue = value;
setValues((values = {}) => {
const newValues = {
...values,
Expand All @@ -223,22 +246,40 @@ export const SessionEditingForm = ({
return newValues;
});
},
[fields]
[]
);

const handleBlur = useCallback(
(evt: FocusEvent) => {
const handleBlur = useCallback<FocusEventHandler<HTMLInputElement>>(
(evt) => {
const [field, value] = getFieldNameAndValue(evt);
const { type } = getField(fields, field);
const rowKey = values?.[keyField];
const typedValue = getTypedValue(value, type, true);
// TODO link this with client side validation if we're going to use it
const { type } = getField(fields, field);
const clientTypedValue = getTypedValue(value, type, true);
console.log(`client typed value ${clientTypedValue}`);
const typedValue = value;
if (typeof rowKey === "string") {
dataSource.menuRpcCall({
rowKey,
field: field,
value: typedValue,
type: "VP_EDIT_CELL_RPC",
});
dataSource
.menuRpcCall({
rowKey,
field: field,
value: typedValue,
type: "VP_EDIT_CELL_RPC",
})
.then((response) => {
if (response?.type === "VP_EDIT_RPC_REJECT") {
console.log(`edit rejected ${response.error}`);
setFieldStatusValues((map) => ({
...map,
[field]: response.error,
}));
} else {
setFieldStatusValues((map) => ({
...map,
[field]: undefined,
}));
}
});
}
},
[dataSource, fields, keyField, values]
Expand All @@ -248,7 +289,7 @@ export const SessionEditingForm = ({
(action: unknown) => {
if (typeof action === "object" && action !== null) {
if ("type" in action && action.type === "CLOSE_DIALOG_ACTION") {
onClose();
onClose?.();
}
}
},
Expand Down Expand Up @@ -276,7 +317,7 @@ export const SessionEditingForm = ({
);

const handleCancel = useCallback(() => {
onClose();
onClose?.();
}, [onClose]);

const getFormControl = (field: FormFieldDescriptor) => {
Expand All @@ -287,12 +328,10 @@ export const SessionEditingForm = ({
);
} else {
return (
<input
<Input
className={`${classBase}-fieldValue`}
data-field={field.name}
onBlur={handleBlur}
onChange={handleChange}
type="text"
value={value}
id={`${id}-input-${field.name}`}
/>
Expand Down Expand Up @@ -340,17 +379,22 @@ export const SessionEditingForm = ({
onKeyDown={handleKeyDown}
>
{fields.map((field) => (
<div className={`${classBase}-field`} key={field.name}>
<label
className={cx(`${classBase}-fieldLabel`, {
[`${classBase}-required`]: field.required,
})}
htmlFor={`${id}-input-${field.name}`}
>
{field?.label ?? field.description}
</label>
<FormField
className={`${classBase}-field`}
data-field={field.name}
key={field.name}
necessity={field.required ? "required" : "optional"}
readOnly={field.readonly}
validationStatus={
fieldStatusValues[field.name] ? "error" : undefined
}
>
<FormFieldLabel>{field?.label ?? field.description}</FormFieldLabel>
{getFormControl(field)}
</div>
<FormFieldHelperText>
{fieldStatusValues[field.name] ?? ""}
</FormFieldHelperText>
</FormField>
))}
</div>
<div className={`${classBase}-buttonbar salt-theme salt-density-high`}>
Expand Down
Loading

0 comments on commit 907b335

Please sign in to comment.