Skip to content

Commit

Permalink
Merge pull request #926 from centerofci/type_config
Browse files Browse the repository at this point in the history
Type configuration for Text, Number, Boolean, and all mathesar types with single associated db type
  • Loading branch information
seancolsen committed Feb 4, 2022
2 parents afac354 + afc2f77 commit db9fa80
Show file tree
Hide file tree
Showing 42 changed files with 1,065 additions and 246 deletions.
1 change: 1 addition & 0 deletions mathesar_ui/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ module.exports = {
},
settings: {
'svelte3/typescript': () => typescript,
'svelte3/ignore-styles': ({ lang }) => lang === 'scss',
'import/resolver': {
node: {
extensions: ['.js', '.ts'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Option } from '@mathesar-component-library-dir/types';
import FieldsetGroup from '@mathesar-component-library-dir/fieldset-group/FieldsetGroup.svelte';
import Checkbox from '@mathesar-component-library-dir/checkbox/Checkbox.svelte';
import { ImmutableSet } from '@mathesar-component-library-dir/common/utils/ImmutableSet';
import ImmutableSet from '@mathesar-component-library-dir/common/utils/ImmutableSet';
export let values: Option['value'][] = [];
export let isInline = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class ImmutableSet<T extends string | number | boolean | null> {
export default class ImmutableSet<T extends string | number | boolean | null> {
private set: Set<T>;

constructor(i?: Iterable<T>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ImmutableSet } from '../ImmutableSet';
import ImmutableSet from '../ImmutableSet';

test('ImmutableSet', () => {
const s = new ImmutableSet<number>([7, 8]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { executeRule } from '../ruleExecuter';
import type { Rule } from '../ruleExecuter';

describe('Rule executer', () => {
test('Single term rule', () => {
const rule: Rule = {
id: 'restrictFieldSize',
op: 'eq',
value: false,
};

expect(executeRule(rule, { restrictFieldSize: false })).toBe(true);
expect(executeRule(rule, { restrictFieldSize: true })).toBe(false);
expect(executeRule(rule, {})).toBe(false);
});

test('Multi term rule with and', () => {
const rule: Rule = {
combination: 'and',
terms: [
{
id: 'restrictFieldSize',
op: 'eq',
value: true,
},
{
id: 'fieldSizeLimit',
op: 'lte',
value: 255,
},
],
};

expect(
executeRule(rule, {
restrictFieldSize: true,
fieldSizeLimit: 200,
}),
).toBe(true);
expect(
executeRule(rule, {
restrictFieldSize: false,
fieldSizeLimit: 200,
}),
).toBe(false);
expect(
executeRule(rule, {
restrictFieldSize: true,
fieldSizeLimit: 300,
}),
).toBe(false);
expect(
executeRule(rule, {
restrictFieldSize: false,
fieldSizeLimit: 300,
}),
).toBe(false);
expect(executeRule(rule, {})).toBe(false);
});

test('Multi term rule with or', () => {
const rule: Rule = {
combination: 'or',
terms: [
{
id: 'restrictFieldSize',
op: 'eq',
value: true,
},
{
id: 'fieldSizeLimit',
op: 'lte',
value: 255,
},
],
};

expect(
executeRule(rule, {
restrictFieldSize: true,
fieldSizeLimit: 200,
}),
).toBe(true);
expect(
executeRule(rule, {
restrictFieldSize: false,
fieldSizeLimit: 200,
}),
).toBe(true);
expect(
executeRule(rule, {
restrictFieldSize: true,
fieldSizeLimit: 300,
}),
).toBe(true);
expect(
executeRule(rule, {
restrictFieldSize: false,
fieldSizeLimit: 300,
}),
).toBe(false);
expect(executeRule(rule, {})).toBe(false);
});
});
2 changes: 0 additions & 2 deletions mathesar_ui/src/component-library/common/utils/filterUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,3 @@ export function filterTree(
.indexOf(searchTerm.toLowerCase()) > -1,
);
}

export default {};
2 changes: 0 additions & 2 deletions mathesar_ui/src/component-library/common/utils/formatUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@ export function formatSize(sizeInBytes: number): string {

return `${repValue.toFixed(2)} ${repUnit}B`;
}

export default {};
12 changes: 12 additions & 0 deletions mathesar_ui/src/component-library/common/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Utility Classes
export { default as CancellablePromise } from './CancellablePromise';
export { default as EventHandler } from './EventHandler';
export { default as ImmutableSet } from './ImmutableSet';

// Utility Functions
export * from './domUtils';
export * from './filterUtils';
export * from './formatUtils';
export * from './pauseableTweened';
export * from './ruleExecuter';
export * from './storeUtils';
66 changes: 66 additions & 0 deletions mathesar_ui/src/component-library/common/utils/ruleExecuter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
export type RuleTermOperator = 'eq' | 'neq' | 'lt' | 'gt' | 'lte' | 'gte';

export interface RuleTerm {
id: string;
op: RuleTermOperator;
value: unknown;
}

export interface RuleTermCombination {
combination: 'and' | 'or';
terms: Rule[];
}

export type Rule = RuleTerm | RuleTermCombination;

function combine(
combination: RuleTermCombination['combination'],
term1: boolean,
term2: boolean,
): boolean {
if (combination === 'and') {
return term1 && term2;
}
return term1 || term2;
}

function executureTerm(
term: RuleTerm,
values: Record<RuleTerm['id'], unknown>,
): boolean {
const termValue = values[term.id];
switch (term.op) {
case 'eq':
return termValue === term.value;
case 'neq':
return termValue !== term.value;
case 'gt':
return termValue > term.value;
case 'gte':
return termValue >= term.value;
case 'lt':
return termValue < term.value;
case 'lte':
return termValue <= term.value;
default:
return false;
}
}

export function executeRule(
rule: Rule,
values: Record<RuleTerm['id'], unknown>,
): boolean {
if ('combination' in rule) {
let isSuccess = rule.combination === 'and';
rule.terms.forEach((term) => {
isSuccess = combine(
rule.combination,
isSuccess,
executeRule(term, values),
);
});
return isSuccess;
}
return executureTerm(rule, values);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.form {
.form-builder {
.form-element + .form-element {
margin-top: 0.6rem;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
}
</script>

<div class="form">
<div class="form-builder">
<form on:submit|preventDefault={submit}>
<FormElement
stores={form.stores}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,22 @@
<FormInput
{...element}
{...variables[element.variable]}
store={stores[element.variable]}
store={stores.get(element.variable)}
/>
{:else if element.type === 'switch'}
<Switch
store={stores[element.variable]}
store={stores.get(element.variable)}
cases={element.cases}
let:element={childElement}
>
<svelte:self {variables} {stores} element={childElement} />
</Switch>
{:else if element.type === 'if'}
<If store={stores[element.variable]} {...element} let:element={childElement}>
<If
store={stores.get(element.variable)}
{...element}
let:element={childElement}
>
<svelte:self {variables} {stores} element={childElement} />
</If>
{:else}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Story, Canvas, ArgsTable, Source } from '@storybook/addon-docs/blocks';
import Form from '../Form.svelte';
import FormBuilder from '../FormBuilder.svelte';

# Form

Expand All @@ -9,12 +9,12 @@ Form system design specification.

<Source language='html' code={`
<script>
import { makeForm, Form } from '@mathesar-component-library';
import { makeForm, FormBuilder } from '@mathesar-component-library';
const formConfig = { ... };
const form = makeForm(formConfig);
<\/script>
<Form {form}/>
<FormBuilder {form}/>
`}/>

### Basic example
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import { Meta, Story } from '@storybook/addon-svelte-csf';
import FormStory from './FormStory.svelte';
import FormBuilderStory from './FormBuilderStory.svelte';
import BasicForm from './basicForm';
import FormWithIfCondition from './formWithIfCondition';
import FormWithSwitchCondition from './formWithSwitchCondition';
import Docs from './Form.mdx';
import Docs from './FormBuilder.mdx';
const meta = {
title: 'Systems/Form',
title: 'Systems/FormBuilder',
parameters: {
docs: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
Expand All @@ -23,13 +23,13 @@
<Meta {...meta} />

<Story name="Basic">
<FormStory formConfig={BasicForm} />
<FormBuilderStory formConfig={BasicForm} />
</Story>

<Story name="If Condition">
<FormStory formConfig={FormWithIfCondition} />
<FormBuilderStory formConfig={FormWithIfCondition} />
</Story>

<Story name="Switch Condition">
<FormStory formConfig={FormWithSwitchCondition} />
<FormBuilderStory formConfig={FormWithSwitchCondition} />
</Story>
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import { makeForm, Form } from '../index';
import { makeForm, FormBuilder } from '../index';
export let formConfig;
$: form = makeForm(formConfig);
</script>

<p>
<Form {form} />
<FormBuilder {form} />
</p>

<br />
Expand Down
37 changes: 33 additions & 4 deletions mathesar_ui/src/component-library/form-builder/formFactory.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
import { writable } from 'svelte/store';
import type { FormConfiguration, FormBuildConfiguration } from './types';
import { derived, get, writable } from 'svelte/store';
import type { Readable } from 'svelte/store';
import type {
FormInputDataType,
FormConfiguration,
FormBuildConfiguration,
FormValues,
} from './types';

export function makeForm(
formConfig: FormConfiguration,
formValues?: FormValues,
): FormBuildConfiguration {
const stores = {};
const stores: FormBuildConfiguration['stores'] = new Map();
Object.keys(formConfig.variables)?.forEach((key) => {
stores[key] = writable(formConfig.variables[key].default);
const value =
typeof formValues?.[key] !== 'undefined'
? formValues[key]
: formConfig.variables[key].default;
stores.set(key, writable(value));
});

const values: Readable<Record<string, FormInputDataType>> = derived(
[...stores.values()],
(storeValues) => {
const valueObj = {};
[...stores.keys()].forEach((key, index) => {
valueObj[key] = storeValues[index];
});
return valueObj;
},
);

function getValues(): ReturnType<FormBuildConfiguration['getValues']> {
return get(values);
}

return {
...formConfig,
stores,
values,
getValues,
};
}
2 changes: 1 addition & 1 deletion mathesar_ui/src/component-library/form-builder/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { makeForm } from './formFactory';
export { default as Form } from './Form.svelte';
export { default as FormBuilder } from './FormBuilder.svelte';
Loading

0 comments on commit db9fa80

Please sign in to comment.