Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display options groundwork & config for boolean, number #1239

Merged
merged 23 commits into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2abd34f
Setup types for display form
pavish Mar 18, 2022
bc526d6
Show display options tab for types with display options
pavish Mar 22, 2022
0e96f27
Display options component initial commit
pavish Mar 22, 2022
475a31e
Merge branch 'master' of https://github.com/centerofci/mathesar into …
pavish Mar 22, 2022
3794a14
Merge branch 'master' of https://github.com/centerofci/mathesar into …
pavish Mar 24, 2022
5065a62
Move store utils to component library utilities
pavish Mar 24, 2022
6409615
Remove view layer dependency on form validation
pavish Mar 24, 2022
5ab8a62
Add unit tests for form factory
pavish Mar 24, 2022
50525b7
Move construction of options to form value and vise versa to abstract…
pavish Mar 24, 2022
ab34550
Merge branch 'master' of https://github.com/centerofci/mathesar into …
pavish Mar 24, 2022
e8b1ded
Merge branch 'master' of https://github.com/centerofci/mathesar into …
pavish Mar 25, 2022
116092e
Move forms to type options menu level to perform combined validation …
pavish Mar 25, 2022
f6090c8
Reorganize type configuration components
pavish Mar 25, 2022
e893b79
Merge branch 'master' of https://github.com/centerofci/mathesar into …
pavish Mar 25, 2022
f360eb8
Merge branch 'master' of https://github.com/centerofci/mathesar into …
pavish Mar 26, 2022
f057bf3
Merge branch 'master' of https://github.com/centerofci/mathesar into …
pavish Mar 26, 2022
34cb167
Show error indication on type configuration tabs
pavish Mar 26, 2022
40a3f03
Add number display options
pavish Mar 26, 2022
ea8d277
Fix number db configuration, do not set defaults for precision and scale
pavish Mar 26, 2022
66f47a0
Merge branch 'select_component_refactor' of https://github.com/center…
pavish Mar 27, 2022
5cf372a
Merge branch 'master' into display_options
pavish Mar 27, 2022
9bbe009
Merge branch 'master' into display_options
pavish Mar 28, 2022
52385b3
Merge branch 'master' into display_options
seancolsen Mar 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mathesar_ui/src/component-library/button/Button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ button.btn {
border-radius: 4px;
box-shadow: #000 0 0 0 0, #000 0 0 0 0, rgba(0, 0, 0, 0.05) 0 1px 2px 0;
font-weight: inherit;
position: relative;

> * + * {
margin-left: 4px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
}

&:disabled {
background: #efefef;
background: var(--input-disabled-background);
}

&.has-error {
background: #fde9e9;
background: var(--danger-background-color);
}
}
1 change: 1 addition & 0 deletions mathesar_ui/src/component-library/common/styles/main.scss
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
@import './variables.scss';
@import 'components/input.scss';
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/**
* File to place common css & scss variables for the component library
*/
:root {
// Danger colors
--danger-color: #d67171;
--danger-background-color: #fde9e9;
--dander-border-color: #f2aaaa;

// Input specific
--input-disabled-background: #efefef;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { get, writable } from 'svelte/store';
import { collapse, unite } from './storeUtils';
import { collapse, unite } from '../storeUtils';

test('collapse', () => {
const innerA = writable(21);
Expand Down
41 changes: 40 additions & 1 deletion mathesar_ui/src/component-library/common/utils/storeUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Readable } from 'svelte/store';
import { readable } from 'svelte/store';
import { readable, derived } from 'svelte/store';

export function isReadable<T>(v: Readable<T> | T): v is Readable<T> {
return (
Expand All @@ -15,3 +15,42 @@ export function ensureReadable<T>(v: Readable<T> | T): Readable<T> {
}
return readable(v);
}

/**
* Collapse two nested stores into a single store.
*/
export function collapse<T>(outerStore: Readable<Readable<T>>): Readable<T> {
// This is memory-safe because the Unsubscriber function gets returned from
// the callback passed to `derive`.
//
// From https://svelte.dev/docs#run-time-svelte-store-derived
//
// > If you return a function from the callback, it will be called when a) the
// > callback runs again, or b) the last subscriber unsubscribes.
return derived(outerStore, (innerStore, set) => innerStore.subscribe(set));
}

function arrayWithValueSetAtIndex<T>(array: T[], index: number, item: T): T[] {
const result = [...array];
result[index] = item;
return result;
}

/**
* Unite an array of stores into a store of arrays.
*/
export function unite<T>(stores: Readable<T>[]): Readable<T[]> {
let results: T[] = [];
return readable(results, (set) => {
const unsubscribers = stores.map((store, index) =>
store.subscribe((value) => {
results = arrayWithValueSetAtIndex(results, index, value);
set(results);
}),
);
// This is memory safe because when the last subscriber unsubscribes from
// the `unite` store, the function below will ensure that we're
// unsubscribing from all the inner stores.
return () => unsubscribers.forEach((unsubscriber) => unsubscriber());
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
.form-input {
.validation-error {
margin-top: 5px;
color: #d67171;
color: var(--danger-color);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
const dispatch = createEventDispatcher();

export let form: FormBuildConfiguration;
$: validationStore = form.validation;
$: validationStore = form.validationStore;

function submit() {
dispatch('submit');
Expand All @@ -18,7 +18,6 @@
<form on:submit|preventDefault={submit}>
<FormElement
stores={form.stores}
storeUsage={form.storeUsage}
variables={form.variables}
element={form.layout}
validationResult={$validationStore}
Expand Down
28 changes: 0 additions & 28 deletions mathesar_ui/src/component-library/form-builder/FormElement.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import { onMount } from 'svelte';
import FormInput from './FormInput.svelte';
import FormLayout from './FormLayout.svelte';
import Switch from './Switch.svelte';
Expand All @@ -12,37 +11,10 @@

export let element: FormElement;
export let stores: FormBuildConfiguration['stores'];
export let storeUsage: FormBuildConfiguration['storeUsage'];
export let variables: FormBuildConfiguration['variables'];
export let validationResult: FormValidationResult;

$: store = 'variable' in element ? stores.get(element.variable) : undefined;

onMount(() => {
if ('variable' in element) {
const variableName = element.variable;
storeUsage.update((existingMap) => {
const newMap = new Map(existingMap);
const existingCount = newMap.get(variableName) ?? 0;
newMap.set(variableName, existingCount + 1);
return newMap;
});

return () => {
storeUsage.update((existingMap) => {
const newMap = new Map(existingMap);
const existingCount = newMap.get(variableName) ?? 0;
if (existingCount > 0) {
newMap.set(variableName, existingCount - 1);
} else {
newMap.delete(variableName);
}
return newMap;
});
};
}
return () => {};
});
</script>

{#if element.type === 'input' && store}
Expand Down
16 changes: 9 additions & 7 deletions mathesar_ui/src/component-library/form-builder/If.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
<script lang="ts">
import { checkCondition } from './utils';
import { computeIfElements } from './utils';
import type { FormInputStore, ConditionalIfElement } from './types';

export let store: FormInputStore;
export let condition: ConditionalIfElement['condition'] = 'eq';
export let value: ConditionalIfElement['value'];
export let elements: ConditionalIfElement['elements'];

$: isConditionSatisfied = checkCondition($store, condition, value);
$: elementsToDisplay = computeIfElements($store, {
condition,
value,
elements,
});
</script>

{#if isConditionSatisfied}
{#each elements || [] as element (element)}
<slot {element} />
{/each}
{/if}
{#each elementsToDisplay as element (element)}
<slot {element} />
{/each}
6 changes: 3 additions & 3 deletions mathesar_ui/src/component-library/form-builder/Switch.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import { computeSwitchElements } from './utils';
import type { FormInputStore, ConditionalSwitchElement } from './types';

export let store: FormInputStore;
export let cases: ConditionalSwitchElement['cases'];

// @ts-ignore: https://github.com/centerofci/mathesar/issues/1055
$: elements = cases[$store?.toString()] || cases.default || [];
$: elementsToDisplay = computeSwitchElements($store, { cases });
</script>

{#each elements as element (element)}
{#each elementsToDisplay as element (element)}
<slot {element} />
{/each}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { get } from 'svelte/store';
import { makeForm } from '../formFactory';

describe('Form factory tests', () => {
test('values should only return values of stores in use', () => {
const form = makeForm({
variables: {
valueInUse: { type: 'string', default: 'valueInUse' },
conditionalValue: { type: 'string', default: 'valid' },
valueNotInUse: { type: 'string', default: 'valueNotInUse' },
valueHiddenByIf: { type: 'string', default: 'valueHiddenByIf' },
valueShownByIf: { type: 'string', default: 'valueShownByIf' },
valueHiddenBySwitch: { type: 'string', default: 'valueHiddenBySwitch' },
valueShownBySwitch: { type: 'string', default: 'valueShownBySwitch' },
},
layout: {
orientation: 'vertical',
elements: [
{
type: 'input',
variable: 'valueInUse',
},
{
type: 'if',
variable: 'conditionalValue',
condition: 'eq',
value: 'valid',
elements: [
{
type: 'input',
variable: 'valueShownByIf',
},
],
},
{
type: 'if',
variable: 'conditionalValue',
condition: 'eq',
value: 'invalid',
elements: [
{
type: 'input',
variable: 'valueHiddenByIf',
},
],
},
{
type: 'switch',
variable: 'conditionalValue',
cases: {
valid: [
{
type: 'input',
variable: 'valueShownBySwitch',
},
],
invalid: [
{
type: 'input',
variable: 'valueHiddenBySwitch',
},
],
},
},
],
},
});

expect(get(form.values)).toEqual(
expect.objectContaining({
valueInUse: 'valueInUse',
valueShownByIf: 'valueShownByIf',
valueShownBySwitch: 'valueShownBySwitch',
}),
);
});

test('validation', () => {
const form = makeForm(
{
variables: {
validVariable: {
type: 'string',
validation: {
checks: ['isEmpty'],
},
},
invalidVariable: {
type: 'string',
validation: {
checks: ['isEmpty'],
},
},
},
layout: {
orientation: 'vertical',
elements: [
{
type: 'input',
variable: 'validVariable',
},
{
type: 'input',
variable: 'invalidVariable',
},
],
},
},
{
validVariable: 'valid',
},
);
expect(form.getValidationResult()).toEqual(
expect.objectContaining({
isValid: false,
failedChecks: {
invalidVariable: ['isEmpty'],
},
}),
);
form.stores.get('invalidVariable')?.set('validValue');
expect(form.getValidationResult()).toEqual(
expect.objectContaining({
isValid: true,
failedChecks: {},
}),
);
});
});
Loading