Skip to content

Commit

Permalink
Merge pull request #3319 from mathesar-foundation/3311_new_db_conn
Browse files Browse the repository at this point in the history
New DB connection form
  • Loading branch information
pavish authored Jan 8, 2024
2 parents fc3422a + 66a73c3 commit 429d057
Show file tree
Hide file tree
Showing 39 changed files with 943 additions and 205 deletions.
2 changes: 2 additions & 0 deletions mathesar_ui/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ module.exports = {
'DEFAULT',
'Mathesar.org',
'NULL',
'@',
'/',
'*',
'+',
':',
Expand Down
2 changes: 1 addition & 1 deletion mathesar_ui/src/AppTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TreeItem } from '@mathesar-component-library/types';

/** @deprecated Use either Connection or ConnectionModel interface instead */
/** @deprecated in favor of Connection */
export interface Database {
id: number;
nickname: string;
Expand Down
72 changes: 70 additions & 2 deletions mathesar_ui/src/api/connections.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
addQueryParamsToUrl,
deleteAPI,
getAPI,
patchAPI,
postAPI,
type PaginatedResponse,
deleteAPI,
addQueryParamsToUrl,
} from './utils/requestUtils';

export interface Connection {
Expand All @@ -27,6 +28,70 @@ function list() {
);
}

export const sampleDataOptions = [
'library_management',
'movie_collection',
] as const;

export type SampleDataSchemaIdentifier = (typeof sampleDataOptions)[number];

export type ConnectionReference =
| { connection_type: 'internal_database' }
| { connection_type: 'user_database'; id: Connection['id'] };

export interface CommonCreationProps {
database_name: string;
sample_data: SampleDataSchemaIdentifier[];
nickname: string;
}

export interface CreateFromKnownConnectionProps extends CommonCreationProps {
credentials: { connection: ConnectionReference };
create_database: boolean;
}

/**
* The `create_database` field is not present on this variant because we do not
* support creating the database in this case. The reason for this restriction
* is that we need a valid connection to an existing database in order to first
* create one. In theory, we could ask the user to supply such a connection but
* Sean and Brent deemed that approach to add too much additional complexity to
* the UI to justify its inclusion. We predict that if someone already has a
* PostgreSQL user, they are likely to already have a PostgreSQL database too.
*/
export interface CreateFromScratchProps extends CommonCreationProps {
credentials: {
user: string;
password: string;
host: string;
port: string;
};
}

export interface CreateWithNewUserProps extends CommonCreationProps {
credentials: {
create_user_via: ConnectionReference;
user: string;
password: string;
};
create_database: boolean;
}

function createFromKnownConnection(props: CreateFromKnownConnectionProps) {
const url = '/api/ui/v0/connections/create_from_known_connection/';
return postAPI<Connection>(url, props);
}

function createFromScratch(props: CreateFromScratchProps) {
const url = '/api/ui/v0/connections/create_from_scratch/';
return postAPI<Connection>(url, props);
}

function createWithNewUser(props: CreateWithNewUserProps) {
const url = '/api/ui/v0/connections/create_with_new_user/';
return postAPI<Connection>(url, props);
}

function update(
connectionId: Connection['id'],
properties: Partial<UpdatableConnectionProperties>,
Expand All @@ -51,6 +116,9 @@ function deleteConnection(

export default {
list,
createFromKnownConnection,
createFromScratch,
createWithNewUser,
update,
delete: deleteConnection,
};
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
<script lang="ts">
import FieldsetGroup from '@mathesar-component-library-dir/fieldset-group/FieldsetGroup.svelte';
import type { SvelteComponent } from 'svelte';
import Checkbox from '@mathesar-component-library-dir/checkbox/Checkbox.svelte';
import type { LabelGetter } from '@mathesar-component-library-dir/common/utils/formatUtils';
import FieldsetGroup from '@mathesar-component-library-dir/fieldset-group/FieldsetGroup.svelte';
import type { ComponentWithProps } from '../types';
type Option = $$Generic;
export let values: Option[] = [];
export let isInline = false;
export let options: Option[] = [];
export let options: readonly Option[] = [];
export let label: string | undefined = undefined;
export let ariaLabel: string | undefined = undefined;
export let checkboxLabelKey: string | undefined = undefined;
export let getCheckboxLabel: LabelGetter<Option> | undefined = undefined;
export let getCheckboxHelp: <C extends SvelteComponent>(
value: Option,
) => string | ComponentWithProps<C> | undefined = () => undefined;
export let getCheckboxDisabled: (value: Option | undefined) => boolean = () =>
false;
/**
* By default, options will be compared by equality. If you're using objects as
* options, you can supply a custom function here to compare them.
Expand Down Expand Up @@ -41,12 +50,15 @@
{isInline}
{options}
{label}
{ariaLabel}
{disabled}
let:option
let:disabled={innerDisabled}
on:change
labelKey={checkboxLabelKey}
getLabel={getCheckboxLabel}
getHelp={getCheckboxHelp}
getDisabled={getCheckboxDisabled}
>
<Checkbox
on:change={({ detail: checked }) => handleChange(option, checked)}
Expand Down
10 changes: 10 additions & 0 deletions mathesar_ui/src/component-library/common/utils/typeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ export function requiredNonNullable<T>(
return defaultValue;
}

/**
* Used to make sure that a value is defined before using it.
*/
export function defined<T, U>(
v: T | undefined,
f: (v: T) => U | undefined,
): U | undefined {
return v === undefined ? undefined : f(v);
}

/**
* From https://stackoverflow.com/a/51365037/895563
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.fieldset-group {
padding: 0;
margin: 0;
--spacing-y-default: 0.5em;
--spacing-x-default: 1em;

Expand All @@ -14,10 +15,15 @@
&.has-label .options {
margin-top: 1em;
}
&:not(.inline) .option {
&:not(.inline) .option:not(:first-child) {
margin-top: var(--spacing-y, var(--spacing-y-default));
}
&:not(.inline) .option:not(:last-child) {
margin-bottom: var(--spacing-y, var(--spacing-y-default));
}
&:not(.inline) .option.has-help:not(:last-child) {
margin-bottom: calc(2 * var(--spacing-y, var(--spacing-y-default)));
}

&.inline .options {
display: flex;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
<script lang="ts">
import LabeledInput from '@mathesar-component-library-dir/labeled-input/LabeledInput.svelte';
import type { SvelteComponent } from 'svelte';
import type { LabelGetter } from '@mathesar-component-library-dir/common/utils/formatUtils';
import { getLabel as defaultGetLabel } from '@mathesar-component-library-dir/common/utils/formatUtils';
import LabeledInput from '@mathesar-component-library-dir/labeled-input/LabeledInput.svelte';
import Render from '@mathesar-component-library-dir/render/Render.svelte';
import StringOrComponent from '@mathesar-component-library-dir/string-or-component/StringOrComponent.svelte';
import type { ComponentWithProps } from '../types';
type Option = $$Generic;
export let isInline = false;
export let options: Option[] = [];
export let options: readonly Option[] = [];
export let label: string | undefined = undefined;
export let ariaLabel: string | undefined = undefined;
export let disabled = false;
export let labelKey = 'label';
export let getLabel: LabelGetter<Option> = (o: Option) =>
defaultGetLabel(o, labelKey);
export let boxed = false;
export let getDisabled: (value: Option | undefined) => boolean = () => false;
export let getHelp: <C extends SvelteComponent>(
value: Option,
) => string | ComponentWithProps<C> | undefined = () => undefined;
</script>

<fieldset
Expand All @@ -35,12 +41,12 @@
{/if}
<ul class="options">
{#each options as option (option)}
<li class="option">
{@const help = getHelp(option)}
<li class="option" class:has-help={!!help}>
<LabeledInput layout="inline-input-first">
<svelte:fragment slot="label">
<StringOrComponent arg={getLabel(option)} />
</svelte:fragment>
<StringOrComponent slot="label" arg={getLabel(option)} />
<slot {option} disabled={getDisabled(option) || disabled} />
<Render slot="help" arg={help} />
</LabeledInput>
</li>
{/each}
Expand Down
1 change: 1 addition & 0 deletions mathesar_ui/src/component-library/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export { default as PasswordInput } from './password-input/PasswordInput.svelte'
export { default as Progress } from './progress/Progress.svelte';
export { default as Radio } from './radio/Radio.svelte';
export { default as RadioGroup } from './radio-group/RadioGroup.svelte';
export { default as Render } from './render/Render.svelte';
export { default as Skeleton } from './skeleton/Skeleton.svelte';
export { default as Spinner } from './spinner/Spinner.svelte';
export { default as SpinnerArea } from './spinner-area/SpinnerArea.svelte';
Expand Down
28 changes: 15 additions & 13 deletions mathesar_ui/src/component-library/labeled-input/LabeledInput.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
}

.help {
// TODO figure out how we want to support help text with an inline layout.
// This CSS assumes the layout is stacked
display: block;
font-size: var(--text-size-small);
color: var(--color-text-muted);
Expand All @@ -34,29 +32,33 @@
}
}

&.layout-inline .label-content,
&.layout-inline-input-first .label-content {
// TODO: add support for help text with an inline layout.
&.layout-inline .label-content {
display: inline-flex;
flex-direction: row;
align-items: center; // To support a text input that's taller than the label
.label {
flex: 1 1 auto;
}
.input {
flex: 0 0 auto;
}
}

&.layout-inline .label-content {
flex-direction: row;
align-items: center; // To support a text input that's taller than the label
.input {
margin-left: var(--spacing-x, var(--spacing-x-default));
}
}

&.layout-inline-input-first .label-content {
flex-direction: row-reverse;
align-items: flex-start; // To support a checkbox with a wrapping label
display: grid;
grid-template: auto auto / auto 1fr;
.input {
grid-area: 1 / 1 / 1 / 1;
margin-right: var(--spacing-x, var(--spacing-x-default));
}
.label {
grid-area: 1 / 2 / 1 / 2;
}
.help {
grid-area: 2 / 2 / 2 / 2;
margin-top: var(--spacing-y, var(--spacing-y-default));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
>
<Label>
<span class="label-content">
<span class="label">
{label ?? ''}
<slot name="label" />
</span>
<span class="help">
{help ?? ''}
<slot name="help" />
</span>
<!--
⚠️ NOTE: Do not add any white space within `.label` or `.help`. We have
CSS that uses the `:empty` pseudo-class which does not work if there is
white space.
-->
<span class="label">{label ?? ''}<slot name="label" /></span>
<span class="help">{help ?? ''}<slot name="help" /></span>
<span class="input"><slot /></span>
</span>
</Label>
Expand Down
2 changes: 1 addition & 1 deletion mathesar_ui/src/component-library/list-box/ListBoxTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface ListBoxStaticContextProps<Option> {
export interface ListBoxProps<Option>
extends Partial<ListBoxStaticContextProps<Option>> {
labelKey?: string;
options: Option[] | (() => CancellablePromise<Option[]>);
options: readonly Option[] | (() => CancellablePromise<Option[]>);
value?: Option[];
}

Expand Down
16 changes: 16 additions & 0 deletions mathesar_ui/src/component-library/margin-trim/MarginTrim.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--
@component Trims the outer margins of its children.
-->

<div class="margin-trim">
<slot />
</div>

<style>
.margin-trim > :first-child {
margin-top: 0;
}
.margin-trim > :last-child {
margin-bottom: 0;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
export let value: Option | undefined = undefined;
export let isInline = false;
export let options: Option[] = [];
export let options: readonly Option[] = [];
export let label: string | undefined = undefined;
export let ariaLabel: string | undefined = undefined;
export let radioLabelKey: string | undefined = undefined;
Expand Down
32 changes: 32 additions & 0 deletions mathesar_ui/src/component-library/render/Render.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script lang="ts">
import type { SvelteComponent } from 'svelte';
import MarginTrim from '@mathesar-component-library-dir/margin-trim/MarginTrim.svelte';
import type { ComponentWithProps } from '@mathesar-component-library-dir/types';
import RenderComponentWithProps from './RenderComponentWithProps.svelte';
type T = $$Generic<SvelteComponent>;
export let arg: string | string[] | ComponentWithProps<T> | undefined =
undefined;
</script>

{#if arg === undefined}
{''}
{:else if typeof arg === 'string'}
{arg}
{:else if Array.isArray(arg)}
{#if arg.length === 0}
{''}
{:else if arg.length === 1}
{arg[0]}
{:else}
<MarginTrim>
{#each arg as paragraph}
<p>{paragraph}</p>
{/each}
</MarginTrim>
{/if}
{:else}
<RenderComponentWithProps componentWithProps={arg} />
{/if}
Loading

0 comments on commit 429d057

Please sign in to comment.