set filter modal

This commit is contained in:
Jan Prochazka 2021-03-05 20:29:10 +01:00
parent 1c7052810a
commit bc54564d64
16 changed files with 365 additions and 17 deletions

View File

@ -0,0 +1,149 @@
<script lang="ts" context="module">
</script>
<script lang="ts">
import { showModal } from '../modals/modalTools';
import SetFilterModal from '../modals/SetFilterModal.svelte';
import keycodes from '../utility/keycodes';
import DropDownButton from '../widgets/DropDownButton.svelte';
export let isReadOnly = false;
export let filterType;
export let filter;
export let setFilter;
let value;
function openFilterWindow(condition1) {
showModal(SetFilterModal, { condition1, filterType, onFilter: setFilter });
}
function createMenu() {
switch (filterType) {
case 'number':
return [
{ onClick: () => setFilter(''), text: 'Clear Filter' },
{ onClick: () => filterMultipleValues(), text: 'Filter multiple values' },
{ onClick: () => openFilterWindow('='), text: 'Equals...' },
{ onClick: () => openFilterWindow('['), text: 'Does Not Equal...' },
{ onClick: () => setFilter('NULL'), text: 'Is Null' },
{ onClick: () => setFilter('NOT NULL'), text: 'Is Not Null' },
{ onClick: () => openFilterWindow('>'), text: 'Greater Than...' },
{ onClick: () => openFilterWindow('>='), text: 'Greater Than Or Equal To...' },
{ onClick: () => openFilterWindow('<'), text: 'Less Than...' },
{ onClick: () => openFilterWindow('<='), text: 'Less Than Or Equal To...' },
];
case 'logical':
return [
{ onClick: () => setFilter(''), text: 'Clear Filter' },
{ onClick: () => filterMultipleValues(), text: 'Filter multiple values' },
{ onClick: () => setFilter('NULL'), text: 'Is Null' },
{ onClick: () => setFilter('NOT NULL'), text: 'Is Not Null' },
{ onClick: () => setFilter('TRUE'), text: 'Is True' },
{ onClick: () => setFilter('FALSE'), text: 'Is False' },
{ onClick: () => setFilter('TRUE, NULL'), text: 'Is True or NULL' },
{ onClick: () => setFilter('FALSE, NULL'), text: 'Is False or NULL' },
];
case 'datetime':
return [
{ onClick: () => setFilter(''), text: 'Clear Filter' },
{ onClick: () => filterMultipleValues(), text: 'Filter multiple values' },
{ onClick: () => setFilter('NULL'), text: 'Is Null' },
{ onClick: () => setFilter('NOT NULL'), text: 'Is Not Null' },
{ divider: true },
{ onClick: () => openFilterWindow('<='), text: 'Before...' },
{ onClick: () => openFilterWindow('>='), text: 'After...' },
{ onClick: () => openFilterWindow('>=;<='), text: 'Between...' },
{ divider: true },
{ onClick: () => setFilter('TOMORROW'), text: 'Tomorrow' },
{ onClick: () => setFilter('TODAY'), text: 'Today' },
{ onClick: () => setFilter('YESTERDAY'), text: 'Yesterday' },
{ divider: true },
{ onClick: () => setFilter('NEXT WEEK'), text: 'Next Week' },
{ onClick: () => setFilter('THIS WEEK'), text: 'This Week' },
{ onClick: () => setFilter('LAST WEEK'), text: 'Last Week' },
{ divider: true },
{ onClick: () => setFilter('NEXT MONTH'), text: 'Next Month' },
{ onClick: () => setFilter('THIS MONTH'), text: 'This Month' },
{ onClick: () => setFilter('LAST MONTH'), text: 'Last Month' },
{ divider: true },
{ onClick: () => setFilter('NEXT YEAR'), text: 'Next Year' },
{ onClick: () => setFilter('THIS YEAR'), text: 'This Year' },
{ onClick: () => setFilter('LAST YEAR'), text: 'Last Year' },
{ divider: true },
];
case 'string':
return [
{ onClick: () => setFilter(''), text: 'Clear Filter' },
{ onClick: () => filterMultipleValues(), text: 'Filter multiple values' },
{ onClick: () => openFilterWindow('='), text: 'Equals...' },
{ onClick: () => openFilterWindow('['), text: 'Does Not Equal...' },
{ onClick: () => setFilter('NULL'), text: 'Is Null' },
{ onClick: () => setFilter('NOT NULL'), text: 'Is Not Null' },
{ onClick: () => setFilter('EMPTY, NULL'), text: 'Is Empty Or Null' },
{ onClick: () => setFilter('NOT EMPTY NOT NULL'), text: 'Has Not Empty Value' },
{ divider: true },
{ onClick: () => openFilterWindow('+'), text: 'Contains...' },
{ onClick: () => openFilterWindow('~'), text: 'Does Not Contain...' },
{ onClick: () => openFilterWindow('^'), text: 'Begins With...' },
{ onClick: () => openFilterWindow('!^'), text: 'Does Not Begin With...' },
{ onClick: () => openFilterWindow('$'), text: 'Ends With...' },
{ onClick: () => openFilterWindow('!$'), text: 'Does Not End With...' },
];
}
// return [
// { text: 'Clear filter', onClick: () => (value = '') },
// { text: 'Is Null', onClick: () => (value = 'NULL') },
// { text: 'Is Not Null', onClick: () => (value = 'NOT NULL') },
// ];
}
const handleKeyDown = ev => {
if (isReadOnly) return;
if (ev.keyCode == keycodes.enter) {
setFilter(value);
}
if (ev.keyCode == keycodes.escape) {
setFilter('');
}
// if (ev.keyCode == keycodes.downArrow) {
// if (onFocusGrid) onFocusGrid();
// // ev.stopPropagation();
// ev.preventDefault();
// }
// if (ev.keyCode == KeyCodes.DownArrow || ev.keyCode == KeyCodes.UpArrow) {
// if (this.props.onControlKey) this.props.onControlKey(ev.keyCode);
// }
};
$: value = filter;
// $: if (value != filter) setFilter(value);
</script>
<div class="flex">
<input type="text" readOnly={isReadOnly} bind:value on:keydown={handleKeyDown} />
<DropDownButton icon="icon filter" menu={createMenu} />
</div>
<style>
input {
flex: 1;
min-width: 10px;
}
</style>

View File

@ -45,6 +45,7 @@
import registerCommand from '../commands/registerCommand';
import ColumnHeaderControl from './ColumnHeaderControl.svelte';
import DataGridRow from './DataGridRow.svelte';
import { getFilterType, getFilterValueExpression } from 'dbgate-filterparser';
import {
cellIsSelected,
countColumnSizes,
@ -56,6 +57,9 @@
import { cellFromEvent, emptyCellArray, getCellRange, isRegularCell, nullCell, topLeftCell } from './selection';
import VerticalScrollBar from './VerticalScrollBar.svelte';
import LoadingInfo from '../widgets/LoadingInfo.svelte';
import InlineButton from '../widgets/InlineButton.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import DataFilterControl from './DataFilterControl.svelte';
export let loadNextData = undefined;
export let grider = undefined;
@ -296,6 +300,36 @@
</td>
{/each}
</tr>
{#if display.filterable}
<tr>
<td
class="header-cell"
data-row="filter"
data-col="header"
style={`width:${headerColWidth}px; min-width:${headerColWidth}px; max-width:${headerColWidth}px`}
>
{#if display.filterCount > 0}
<InlineButton on:click={() => display.clearFilters()} square>
<FontIcon icon="icon filter-off" />
</InlineButton>
{/if}
</td>
{#each visibleRealColumns as col (col.uniqueName)}
<td
class="filter-cell"
data-row="filter"
data-col={col.colIndex}
style={`width:${col.width}px; min-width:${col.width}px; max-width:${col.width}px`}
>
<DataFilterControl
filterType={getFilterType(col.dataType)}
filter={display.getFilter(col.uniqueName)}
setFilter={value => display.setFilter(col.uniqueName, value)}
/>
</td>
{/each}
</tr>
{/if}
</thead>
<tbody>
{#each _.range(firstVisibleRowScrollIndex, Math.min(firstVisibleRowScrollIndex + visibleRowCountUpperBound, grider.rowCount)) as rowIndex (rowIndex)}

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { getFormContext } from './FormProviderCore.svelte';
import FromCheckboxFieldRaw from './FromCheckboxFieldRaw.svelte';
import FormCheckboxFieldRaw from './FormCheckboxFieldRaw.svelte';
export let label;
export let name;
@ -18,5 +18,5 @@
{...templateProps}
labelProps={disabled ? { disabled: true } : { onClick: () => setFieldValue(name, !$values[name]) }}
>
<FromCheckboxFieldRaw {name} {...$$restProps} {disabled} />
<FormCheckboxFieldRaw {name} {...$$restProps} {disabled} />
</svelte:component>

View File

@ -0,0 +1,19 @@
<script lang="ts">
import { getFormContext } from './FormProviderCore.svelte';
export let name;
export let value;
export let text;
const { values, setFieldValue } = getFormContext();
</script>
<div>
<input
type="radio"
{name}
checked={$values[name] == value}
on:click={() => setFieldValue(name, value)}
/>
<span on:click={() => setFieldValue(name, value)}>{text}</span>
</div>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { getFormContext } from './FormProviderCore.svelte';
import FromSelectFieldRaw from './FromSelectFieldRaw.svelte';
import FormSelectFieldRaw from './FormSelectFieldRaw.svelte';
export let label;
export let name;
@ -10,5 +10,5 @@
</script>
<svelte:component this={template} type="select" {label} {...templateProps}>
<FromSelectFieldRaw {name} {...$$restProps} />
<FormSelectFieldRaw {name} {...$$restProps} />
</svelte:component>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { getFormContext } from './FormProviderCore.svelte';
import FromTextFieldRaw from './FromTextFieldRaw.svelte';
import FormTextFieldRaw from './FormTextFieldRaw.svelte';
export let label;
export let name;
@ -11,5 +11,5 @@
</script>
<svelte:component this={template} type="text" {label} {...templateProps}>
<FromTextFieldRaw {name} {...$$restProps} {focused} />
<FormTextFieldRaw {name} {...$$restProps} {focused} />
</svelte:component>

View File

@ -7,4 +7,4 @@
const { values, setFieldValue } = getFormContext();
</script>
<TextField {...$$restProps} value={$values[name]} on:change={e => setFieldValue(name, e.target['value'])} />
<TextField {...$$restProps} value={$values[name]} on:input={e => setFieldValue(name, e.target['value'])} />

View File

@ -9,4 +9,4 @@
if (focused) onMount(() => domEditor.focus());
</script>
<input type="text" {...$$restProps} bind:value on:change bind:this={domEditor} />
<input type="text" {...$$restProps} bind:value on:change on:input bind:this={domEditor} />

View File

@ -12,10 +12,9 @@
import FormFieldTemplateLarge from './FormFieldTemplateLarge.svelte';
import ModalBase from './ModalBase.svelte';
import { closeModal } from './modalTools';
import { closeCurrentModal, closeModal } from './modalTools';
export let connection;
export let modalId;
let isTesting;
let sqlConnectResult;
@ -40,7 +39,7 @@
async function handleSubmit(e) {
axios.post('connections/save', e.detail);
closeModal(modalId);
closeCurrentModal();
}
</script>
@ -48,7 +47,7 @@
template={FormFieldTemplateLarge}
initialValues={connection || { server: 'localhost', engine: 'mssql@dbgate-plugin-mssql' }}
>
<ModalBase {...$$restProps} {modalId} noPadding>
<ModalBase {...$$restProps} noPadding>
<div slot="header">Add connection</div>
<TabControl

View File

@ -0,0 +1,77 @@
<script lang="ts">
import FormButton from '../forms/FormButton.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import FormTextFieldRaw from '../forms/FormTextFieldRaw.svelte';
import FormFieldTemplateLarge from './FormFieldTemplateLarge.svelte';
import SetFilterModal_Select from './SetFilterModal_Select.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import FormRadioGroupItem from '../forms/FormRadioGroupItem.svelte';
export let condition1;
export let onFilter;
export let filterType;
const createTerm = (condition, value) => {
if (!value) return null;
if (filterType == 'string') return `${condition}"${value}"`;
return `${condition}${value}`;
};
const handleOk = e => {
const values = e.detail;
const { value1, condition1, value2, condition2, joinOperator } = values;
const term1 = createTerm(condition1, value1);
const term2 = createTerm(condition2, value2);
if (term1 && term2) onFilter(`${term1}${joinOperator}${term2}`);
else if (term1) onFilter(term1);
else if (term2) onFilter(term2);
closeCurrentModal();
};
</script>
<FormProvider initialValues={{ condition1, condition2: '=', joinOperator: ' ' }} template={FormFieldTemplateLarge}>
<ModalBase {...$$restProps}>
<div slot="header">Set filter</div>
<div class="largeFormMarker">
<div class="row">Show rows where</div>
<div class="row">
<div class="col-6 mr-1">
<SetFilterModal_Select {filterType} name="condition1" />
</div>
<div class="col-6 mr-1">
<FormTextFieldRaw name="value1" focused />
</div>
</div>
<div class="row">
<FormRadioGroupItem name="joinOperator" value=" " text="And" />
<FormRadioGroupItem name="joinOperator" value="," text="Or" />
</div>
<div class="row">
<div class="col-6 mr-1">
<SetFilterModal_Select {filterType} name="condition2" />
</div>
<div class="col-6 mr-1">
<FormTextFieldRaw name="value2" />
</div>
</div>
</div>
<div slot="footer">
<FormSubmit value="OK" on:click={handleOk} />
<FormButton type="button" value="Close" on:click={closeCurrentModal} />
</div>
</ModalBase>
</FormProvider>
<style>
.row {
margin: var(--dim-large-form-margin);
display: flex;
}
</style>

View File

@ -0,0 +1,46 @@
<script lang="ts">
import FormSelectFieldRaw from '../forms/FormSelectFieldRaw.svelte';
export let name;
export let filterType;
function getOptions() {
switch (filterType) {
case 'number':
return [
{ value: '=', label: 'eqals' },
{ value: '<>', label: 'does not equal' },
{ value: '<', label: 'is smaller' },
{ value: '>', label: 'is greater' },
{ value: '<=', label: 'is smaller or equal' },
{ value: '>=', label: 'is greater or equal' },
];
case 'string':
return [
{ value: '+', label: 'contains' },
{ value: '~', label: 'does not contain' },
{ value: '^', label: 'begins with' },
{ value: '!^', label: 'does not begin with' },
{ value: '$', label: 'ends with' },
{ value: '!$', label: 'does not end with' },
{ value: '=', label: 'equals' },
{ value: '<>', label: 'does not equal' },
{ value: '<', label: 'is smaller' },
{ value: '>', label: 'is greater' },
{ value: '<=', label: 'is smaller or equal' },
{ value: '>=', label: 'is greater or equal' },
];
case 'datetime':
return [
{ value: '=', label: 'eqals' },
{ value: '<>', label: 'does not equal' },
{ value: '<', label: 'is before' },
{ value: '>', label: 'is after' },
{ value: '<=', label: 'is before or equal' },
{ value: '>=', label: 'is after or equal' },
];
}
}
</script>
<FormSelectFieldRaw {name} options={getOptions()} />

View File

@ -1,5 +1,6 @@
import { openedModals } from '../stores';
import uuidv1 from 'uuid/v1';
import _ from 'lodash';
export function showModal(component, props = {}) {
const modalId = uuidv1();
@ -9,3 +10,7 @@ export function showModal(component, props = {}) {
export function closeModal(modalId) {
openedModals.update(x => x.filter(y => y.modalId != modalId));
}
export function closeCurrentModal() {
openedModals.update(x => _.dropRight(x));
}

View File

@ -1,8 +1,22 @@
<script>
<script lang="ts">
import _ from 'lodash';
import FontIcon from '../icons/FontIcon.svelte';
import { currentDropDownMenu } from '../stores';
import InlineButton from './InlineButton.svelte';
export let icon = 'icon chevron-down';
export let menu;
let domButton;
function handleClick() {
const rect = domButton.getBoundingClientRect();
const left = rect.left;
const top = rect.bottom;
currentDropDownMenu.set({ left, top, items: _.isFunction(menu) ? menu() : menu });
}
</script>
<InlineButton square>
<FontIcon icon="icon chevron-down" />
<InlineButton square on:click={handleClick} bind:this={domButton}>
<FontIcon {icon} />
</InlineButton>

View File

@ -1,9 +1,15 @@
<script lang="ts">
export let disabled = false;
export let square = false;
let domButton;
export function getBoundingClientRect() {
return domButton.getBoundingClientRect();
}
</script>
<div class="outer buttonLike" class:disabled class:square on:click>
<div class="outer buttonLike" class:disabled class:square on:click bind:this={domButton}>
<div class="inner">
<slot />
</div>
@ -41,7 +47,6 @@
background-color: var(--bg-2);
}
.inner {
margin: auto;
flex: 1;