Skip to content

Commit

Permalink
feat(sdf-parser): add first implementation of sdf filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
OliverDudgeon committed Dec 16, 2023
1 parent 4bbd47c commit 4e07937
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 71 deletions.
4 changes: 2 additions & 2 deletions components/ScatterPlot/ScatterPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import dynamic from "next/dynamic";
import type { PlotDatum } from "plotly.js-basic-dist";

import type { Molecule } from "../../features/SDFViewer";
import type { Molecule } from "../../features/SDFViewer/SDFViewerData";

const Plot = dynamic<PlotParams>(
() => import("../../components/viz/Plot").then((mod) => mod.Plot),
Expand All @@ -32,7 +32,7 @@ const getPropArrayFromMolecules = (molecules: Molecule[], prop: string | null) =
if (prop === "id") {
return molecules.map((molecule) => molecule.id);
}
return molecules.map((molecule) => (prop ? molecule.properties[prop] ?? null : null));
return molecules.map((molecule) => (prop ? molecule.properties[prop] : null));
};

type AxisSeries = ReturnType<typeof getPropArrayFromMolecules>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { createColumnHelper } from "@tanstack/react-table";
import { CenterLoader } from "../../../../../../components/CenterLoader";
import { DataTable } from "../../../../../../components/DataTable/DataTable";
import { ModalWrapper } from "../../../../../../components/modals/ModalWrapper";
import type { JSON_SCHEMA_TYPE } from "../../../../../../utils/app/jsonSchema";
import { JSON_SCHEMA_TYPES } from "../../../../../../utils/app/jsonSchema";
import { getErrorMessage } from "../../../../../../utils/next/orvalError";
import { JSON_SCHEMA_TYPES } from "./constants";
import { DatasetSchemaDescriptionInput } from "./DatasetSchemaDescriptionInput";
import { DatasetSchemaInputCell } from "./DatasetSchemaInputCell";
import { DatasetSchemaSelectCell } from "./DatasetSchemaSelectCell";
import type { JSONSchemaType } from "./types";
import { useDatasetSchema } from "./useDatasetSchema";

type TableSchemaView = {
Expand All @@ -23,8 +23,8 @@ type TableSchemaView = {
current: string;
};
type: {
original: JSONSchemaType;
current: JSONSchemaType;
original: JSON_SCHEMA_TYPE;
current: JSON_SCHEMA_TYPE;
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import type { DatasetSchemaGetResponse } from "@squonk/data-manager-client";

import type { JSON_SCHEMA_TYPES } from "./constants";

export type JSONSchemaType = (typeof JSON_SCHEMA_TYPES)[number];
import type { JSON_SCHEMA_TYPE } from "../../../../../../utils/app/jsonSchema";

// These types should be defined in the OpenAPI but currently aren't
export interface Field {
description: string;
type: JSONSchemaType;
type: JSON_SCHEMA_TYPE;
}
export type Fields = Record<string, Field>;
export type FieldKey = keyof Field;
Expand Down
184 changes: 184 additions & 0 deletions features/SDFViewer/ConfigEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { Fragment } from "react";
import type { SubmitHandler } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";

import { Alert, Box, Button, Checkbox, MenuItem, TextField, Typography } from "@mui/material";

import type { SDFViewerConfig } from "../../utils/api/sdfViewer";
import { type JSON_SCHEMA_TYPE, JSON_SCHEMA_TYPES } from "../../utils/app/jsonSchema";

type Field = {
type: JSON_SCHEMA_TYPE;
description: string;
};

interface Schema {
$schema: string;
$id: string;
title: string;
description: string;
version: number;
type: "object";
fields: Record<string, Field>;
required: string[];
labels: Record<string, string>;
}

export interface ConfigEditorProps {
schema: Schema;
config: SDFViewerConfig;
onChange: (config: SDFViewerConfig) => void;
}

const getDefault = (field: string, dtype: JSON_SCHEMA_TYPE) => ({
field,
dtype,
include: true,
cardView: true,
min: -Infinity,
max: Infinity,
sort: "ASC",
});

export const ConfigEditor = ({ schema, config, onChange }: ConfigEditorProps) => {
const { fields } = schema;

const fieldsInConfig = Object.keys(config);
Object.keys(fields).forEach(
(field) =>
!fieldsInConfig.includes(field) && (config[field] = getDefault(field, fields[field].type)),
);

const { control, register, watch, handleSubmit } = useForm<SDFViewerConfig>({
defaultValues: config,
});

if (Object.values(fields).length === 0) {
return <Alert severity="warning">No fields found in schema</Alert>;
}

const getStep = (field: string) => {
const type = watch(field).dtype;
switch (type) {
case "number":
return 0.1;
case "integer":
return 1;
default:
return undefined;
}
};

const getIsNumeric = (key: string) =>
watch(key).dtype === "number" || watch(key).dtype === "integer";

// data isn't really of type SDFViewerConfig, inputs give string values instead of numbers, but
// our Infinity defaults are numbers
const onSubmit: SubmitHandler<SDFViewerConfig> = (data) => onChange(data);

// const onSubmit: SubmitHandler<SDFViewerConfig> = (data) => console.log(data);

return (
<form onSubmit={handleSubmit(onSubmit)}>
<Box display="grid" gap={1} gridTemplateColumns="1fr repeat(6, min-content)">
<Typography component="h3" variant="h4">
Field name
</Typography>
<Typography component="h3" variant="h4">
Type
</Typography>
<Typography component="h3" variant="h4">
Include
</Typography>
<Typography component="h3" variant="h4">
Card view
</Typography>
<Typography component="h3" variant="h4">
Min
</Typography>
<Typography component="h3" variant="h4">
Max
</Typography>
<Typography component="h3" variant="h4">
Sort
</Typography>

{Object.entries(fields).map(([key], index) => (
<Fragment key={`${key}${index}`}>
<Typography>{key}</Typography>
<TextField
select
defaultValue={config[key].dtype}
inputProps={register(`${key}.dtype`)}
>
{JSON_SCHEMA_TYPES.map((type) => (
<MenuItem key={type} value={type}>
{type}
</MenuItem>
))}
</TextField>

<Controller
control={control}
defaultValue={config[key].include}
name={`${key}.include`}
render={({ field }) => (
<Checkbox
{...field}
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
)}
/>
<Controller
control={control}
defaultValue={config[key].cardView}
name={`${key}.cardView`}
render={({ field }) => (
<Checkbox
{...field}
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
)}
/>

<TextField
defaultValue={config[key].min}
disabled={!getIsNumeric(key)}
inputMode="numeric"
inputProps={{ ...register(`${key}.min`), step: getStep(key) }}
sx={{ width: "7em" }}
type="number"
/>
<TextField
defaultValue={config[key].max}
disabled={!getIsNumeric(key)}
inputMode="numeric"
inputProps={{ ...register(`${key}.max`), step: getStep(key) }}
sx={{ width: "7em" }}
type="number"
/>

<TextField
disabled
select
defaultValue={config[key].sort}
inputProps={register(`${key}.sort`)}
>
<MenuItem key="ASC" value="ASC">
ASC
</MenuItem>
<MenuItem key="DESC" value="DESC">
DESC
</MenuItem>
</TextField>
</Fragment>
))}
<Button sx={{ gridColumn: -2 }} type="submit" variant="contained">
Apply
</Button>
</Box>
</form>
);
};
87 changes: 87 additions & 0 deletions features/SDFViewer/SDFViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { PropsWithChildren } from "react";
import { useState } from "react";

import type { DmError, ErrorType } from "@squonk/data-manager-client";
import { useGetProjectFile } from "@squonk/data-manager-client/project";

import { Button, Typography } from "@mui/material";

import { CenterLoader } from "../../components/CenterLoader";
import type { SDFViewerConfig } from "../../utils/api/sdfViewer";
import { ConfigEditor } from "./ConfigEditor";
import { SDFViewerData } from "./SDFViewerData";

export interface SDFViewerProps {
project: string;
path: string;
file: string;
}

const getSchemaFileNameFromSDFFileName = (fname: string) => fname.slice(0, -4) + ".schema.json";

export const SDFViewer = ({ project, path, file }: SDFViewerProps) => {
const schemaFilename = getSchemaFileNameFromSDFFileName(file);
const {
data: schema,
error,
isLoading,
} = useGetProjectFile<any, ErrorType<DmError>>(project, {
path,
file: schemaFilename,
});

const [isEditingConfig, setIsEditingConfig] = useState(true);
const [config, setConfig] = useState<SDFViewerConfig | undefined>(undefined);

if (error) {
// handle error
// SDF schema might not exist
return null;
}

if (isLoading) {
// TODO: add loading page
return (
<Header title={file}>
<CenterLoader />
</Header>
);
}

if (isEditingConfig || config === undefined) {
return (
<Header title={file}>
<ConfigEditor
config={config ?? {}}
schema={schema}
onChange={(config) => {
setIsEditingConfig(false);
setConfig(config);
}}
/>
</Header>
);
}

return (
<Header title={file}>
<Button onClick={() => setIsEditingConfig(true)}>Edit</Button>
<SDFViewerData config={config} file={file} path={path} project={project} />
</Header>
);
};

interface HeaderProps {
title: string;
}

const Header = ({ title, children }: PropsWithChildren<HeaderProps>) => {
return (
<>
<Typography gutterBottom variant="h1">
{title}
</Typography>
{children}
</>
);
};
Loading

0 comments on commit 4e07937

Please sign in to comment.