mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
set filter modal
This commit is contained in:
parent
1c7052810a
commit
bc54564d64
149
packages/web/src/datagrid/DataFilterControl.svelte
Normal file
149
packages/web/src/datagrid/DataFilterControl.svelte
Normal 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>
|
@ -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)}
|
||||
|
@ -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>
|
||||
|
19
packages/web/src/forms/FormRadioGroupItem.svelte
Normal file
19
packages/web/src/forms/FormRadioGroupItem.svelte
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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'])} />
|
@ -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} />
|
||||
|
@ -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
|
||||
|
77
packages/web/src/modals/SetFilterModal.svelte
Normal file
77
packages/web/src/modals/SetFilterModal.svelte
Normal 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>
|
46
packages/web/src/modals/SetFilterModal_Select.svelte
Normal file
46
packages/web/src/modals/SetFilterModal_Select.svelte
Normal 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()} />
|
@ -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));
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user