form view

This commit is contained in:
Jan Prochazka
2021-03-21 17:55:25 +01:00
parent 965a967450
commit 34f2fb2a0a
13 changed files with 329 additions and 10 deletions

View File

@@ -32,6 +32,7 @@ body {
visibility: hidden;
}
.space-between {
display: flex;
justify-content: space-between;
}
.flex {

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
import FormViewFilters from '../formview/FormViewFilters.svelte';
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
import ColumnManager from './ColumnManager.svelte';
@@ -21,10 +22,14 @@
<HorizontalSplitter initialValue="300px" bind:size={managerSize}>
<div class="left" slot="1">
<WidgetColumnBar>
<WidgetColumnBarItem title="Columns" name="columns" height={showReferences ? '40%' : '60%'}>
<WidgetColumnBarItem title="Columns" name="columns" height={showReferences ? '40%' : '60%'} skip={isFormView}>
<ColumnManager {...$$props} {managerSize} />
</WidgetColumnBarItem>
<WidgetColumnBarItem title="Filters" name="filters" height="30%" skip={!isFormView}>
<FormViewFilters {...$$props} {managerSize} />
</WidgetColumnBarItem>
<WidgetColumnBarItem
title="References"
name="references"

View File

@@ -19,6 +19,7 @@
import moment from 'moment';
import _ from 'lodash';
import { isTypeLogical } from 'dbgate-tools';
import ShowFormButton from '../formview/ShowFormButton.svelte';
export let rowIndex;
export let col;
@@ -36,6 +37,7 @@
export let isFocusedColumn = false;
export let domCell = undefined;
export let hideContent = false;
export let onSetFormView;
$: value = (rowData || {})[col.uniqueName];
</script>
@@ -94,6 +96,10 @@
{#if hintFieldsAllowed && hintFieldsAllowed.includes(col.uniqueName) && rowData}
<span class="hint">{rowData[col.hintColumnName]}</span>
{/if}
{#if col.foreignKey && rowData[col.uniqueName]}
<ShowFormButton on:click={() => onSetFormView(rowData, col)} />
{/if}
{/if}
</td>

View File

@@ -209,6 +209,7 @@
import invalidateCommands from '../commands/invalidateCommands';
import createRef from '../utility/createRef';
import { clearLastFocusedFormView } from '../formview/FormView.svelte';
import openReferenceForm, { openPrimaryKeyForm } from '../formview/openReferenceForm';
export let onLoadNextData = undefined;
export let grider = undefined;
@@ -844,6 +845,14 @@
return cells.filter(isRegularCell);
}
function handleSetFormView(rowData, column) {
if (column) {
openReferenceForm(rowData, column, conid, database);
} else {
openPrimaryKeyForm(rowData, display.baseTable, conid, database);
}
}
const [inplaceEditorState, dispatchInsplaceEditor] = createReducer((state, action) => {
switch (action.type) {
case 'show':
@@ -1000,6 +1009,7 @@
inplaceEditorState={$inplaceEditorState}
{dispatchInsplaceEditor}
{frameSelection}
onSetFormView={formViewAvailable && display?.baseTable?.primaryKey ? handleSetFormView : null}
/>
{/each}
</tbody>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import openReferenceForm from '../formview/openReferenceForm';
import DataGridCell from './DataGridCell.svelte';
import { cellIsSelected } from './gridutil';
import InplaceEditor from './InplaceEditor.svelte';
@@ -16,6 +18,7 @@
export let focusedColumn = undefined;
export let inplaceEditorState;
export let dispatchInsplaceEditor;
export let onSetFormView;
$: rowData = grider.getRowData(rowIndex);
$: rowStatus = grider.getRowStatus(rowIndex);
@@ -30,7 +33,7 @@
</script>
<tr style={`height: ${rowHeight}px`}>
<RowHeaderCell {rowIndex} />
<RowHeaderCell {rowIndex} onShowForm={onSetFormView ? () => onSetFormView(rowData, null) : null} />
{#each visibleRealColumns as col (col.uniqueName)}
{#if inplaceEditorState.cell && rowIndex == inplaceEditorState.cell[0] && col.colIndex == inplaceEditorState.cell[1]}
<td>
@@ -58,6 +61,7 @@
(rowStatus.insertedFields && rowStatus.insertedFields.has(col.uniqueName))}
isDeleted={rowStatus.status == 'deleted' ||
(rowStatus.deletedFields && rowStatus.deletedFields.has(col.uniqueName))}
{onSetFormView}
/>
{/if}
{/each}

View File

@@ -1,9 +1,23 @@
<script lang="ts">
import ShowFormButton from '../formview/ShowFormButton.svelte';
export let rowIndex;
export let onShowForm;
let mouseIn = false;
</script>
<td data-row={rowIndex} data-col="header">
<td
data-row={rowIndex}
data-col="header"
on:mouseenter={() => (mouseIn = true)}
on:mouseleave={() => (mouseIn = false)}
>
{rowIndex + 1}
{#if mouseIn && onShowForm}
<ShowFormButton on:click={onShowForm} />
{/if}
</td>
<style>

View File

@@ -100,6 +100,50 @@
onClick: () => getCurrentDataForm().addToFilter(),
});
registerCommand({
id: 'dataForm.goToFirst',
category: 'Data form',
name: 'First',
keyText: 'Ctrl+Home',
toolbar: true,
icon: 'icon arrow-begin',
testEnabled: () => getCurrentDataForm() != null,
onClick: () => getCurrentDataForm().navigate('begin'),
});
registerCommand({
id: 'dataForm.goToPrevious',
category: 'Data form',
name: 'Previous',
keyText: 'Ctrl+ArrowUp',
toolbar: true,
icon: 'icon arrow-left',
testEnabled: () => getCurrentDataForm() != null,
onClick: () => getCurrentDataForm().navigate('previous'),
});
registerCommand({
id: 'dataForm.goToNext',
category: 'Data form',
name: 'Next',
keyText: 'Ctrl+ArrowDown',
toolbar: true,
icon: 'icon arrow-right',
testEnabled: () => getCurrentDataForm() != null,
onClick: () => getCurrentDataForm().navigate('next'),
});
registerCommand({
id: 'dataForm.goToLast',
category: 'Data form',
name: 'Last',
keyText: 'Ctrl+End',
toolbar: true,
icon: 'icon arrow-end',
testEnabled: () => getCurrentDataForm() != null,
onClick: () => getCurrentDataForm().navigate('end'),
});
function isDataCell(cell) {
return cell[1] % 2 == 1;
}
@@ -130,6 +174,7 @@
import createReducer from '../utility/createReducer';
import keycodes from '../utility/keycodes';
import resizeObserver from '../utility/resizeObserver';
import openReferenceForm from './openReferenceForm';
export let conid;
export let database;
@@ -142,6 +187,7 @@
export let former;
export let formDisplay;
export let onSave;
export let onNavigate;
let wrapperHeight = 1;
let rowHeight = 1;
@@ -161,10 +207,18 @@
$: rowData = former?.rowData;
$: rowStatus = former?.rowStatus;
$: rowCount = Math.floor((wrapperHeight - 20) / rowHeight);
$: rowCount = Math.floor((wrapperHeight - 22) / (rowHeight + 2));
$: columnChunks = _.chunk(formDisplay.columns, rowCount) as any[][];
$: rowCountInfo = getRowCountInfo(rowCountBefore, allRowCount);
function getRowCountInfo(rowCountBefore, allRowCount) {
if (rowData == null) return 'No data';
if (allRowCount == null || rowCountBefore == null) return 'Loading row count...';
return `Row: ${(rowCountBefore + 1).toLocaleString()} / ${allRowCount.toLocaleString()}`;
}
export function getTabId() {
return tabid;
}
@@ -173,6 +227,10 @@
return former;
}
export function navigate(command) {
if (onNavigate) onNavigate(command);
}
export function switchToTable() {
setConfig(cfg => ({
...cfg,
@@ -293,7 +351,24 @@
return {};
}, {});
function createMenu() {
return [{ command: 'dataForm.switchToTable' }];
return [
{ command: 'dataForm.switchToTable' },
{ divider: true },
{ command: 'dataForm.filterSelected' },
{ command: 'dataForm.addToFilter' },
{ divider: true },
{ command: 'dataForm.save' },
{ command: 'dataForm.revertRowChanges' },
{ command: 'dataForm.setNull' },
{ divider: true },
{ command: 'dataForm.undo' },
{ command: 'dataForm.redo' },
{ divider: true },
{ command: 'dataForm.goToFirst' },
{ command: 'dataForm.goToPrevious' },
{ command: 'dataForm.goToNext' },
{ command: 'dataForm.goToLast' },
];
}
function handleKeyDown(event) {
@@ -363,6 +438,10 @@
return moveCurrentCell(rowCount - 1, columnChunks.length * 2 - 1);
}
};
function handleSetFormView(rowData, column) {
openReferenceForm(rowData, column, conid, database);
}
</script>
{#if isLoading}
@@ -400,7 +479,11 @@
<FontIcon icon="icon invisible-box" />
{/if}
<span style={`margin-left: ${(col.uniquePath.length - 1) * 20}px`} />
<ColumnLabel {...col} headerText={col.columnName} />
<ColumnLabel
{...col}
headerText={col.columnName}
extInfo={col.foreignKey ? ` -> ${col.foreignKey.refTableName}` : null}
/>
</div>
</td>
<DataGridCell
@@ -411,9 +494,11 @@
isSelected={currentCell[0] == rowIndex && currentCell[1] == chunkIndex * 2 + 1}
isModifiedCell={rowStatus.modifiedFields && rowStatus.modifiedFields.has(col.uniqueName)}
bind:domCell={domCells[`${rowIndex},${chunkIndex * 2 + 1}`]}
hideContent={$inplaceEditorState.cell &&
rowIndex == $inplaceEditorState.cell[0] &&
chunkIndex * 2 + 1 == $inplaceEditorState.cell[1]}
onSetFormView={handleSetFormView}
hideContent={!rowData ||
($inplaceEditorState.cell &&
rowIndex == $inplaceEditorState.cell[0] &&
chunkIndex * 2 + 1 == $inplaceEditorState.cell[1])}
>
{#if $inplaceEditorState.cell && rowIndex == $inplaceEditorState.cell[0] && chunkIndex * 2 + 1 == $inplaceEditorState.cell[1]}
<InplaceEditor
@@ -443,6 +528,11 @@
on:keydown={handleKeyDown}
/>
</div>
{#if rowCountInfo}
<div class="row-count-label">
{rowCountInfo}
</div>
{/if}
</div>
{/if}
@@ -501,4 +591,11 @@
left: -1000px;
top: -1000px;
}
.row-count-label {
position: absolute;
background-color: var(--theme-bg-2);
right: 40px;
bottom: 20px;
}
</style>

View File

@@ -0,0 +1,34 @@
<script lang="ts">
import { getFilterType } from 'dbgate-filterparser';
import DataFilterControl from '../datagrid/DataFilterControl.svelte';
import ColumnLabel from '../elements/ColumnLabel.svelte';
import InlineButton from '../elements/InlineButton.svelte';
import FontIcon from '../icons/FontIcon.svelte';
export let column;
export let formDisplay;
export let filters;
</script>
{#if column}
<div class="m-1">
<div class="space-between">
<ColumnLabel {...column} />
<InlineButton
square
on:click={() => {
formDisplay.removeFilter(column.uniqueName);
}}
>
<FontIcon icon="icon delete" />
</InlineButton>
</div>
<DataFilterControl
filterType={getFilterType(column.dataType)}
filter={filters[column.uniqueName]}
setFilter={value => formDisplay.setFilter(column.uniqueName, value)}
/>
</div>
{/if}

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import _ from 'lodash';
import ManagerInnerContainer from '../elements/ManagerInnerContainer.svelte';
import FormViewFilterColumn from './FormViewFilterColumn.svelte';
import PrimaryKeyFilterEditor from './PrimaryKeyFilterEditor.svelte';
export let managerSize;
export let formDisplay;
$: baseTable = formDisplay?.baseTable;
$: formFilterColumns = formDisplay?.config?.formFilterColumns;
$: filters = formDisplay?.config?.filters;
$: allFilterNames = _.union(_.keys(filters || {}), formFilterColumns || []);
</script>
{#if baseTable?.primaryKey}
<ManagerInnerContainer width={managerSize}>
{#each baseTable.primaryKey.columns as col}
<PrimaryKeyFilterEditor {baseTable} column={col} {formDisplay} />
{/each}
{#each allFilterNames as uniqueName}
<FormViewFilterColumn
column={formDisplay.columns.find(x => x.uniqueName == uniqueName)}
{formDisplay}
{filters}
/>
{/each}
</ManagerInnerContainer>
{/if}

View File

@@ -0,0 +1,51 @@
<script lang="ts">
import ColumnLabel from '../elements/ColumnLabel.svelte';
import InlineButton from '../elements/InlineButton.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import keycodes from '../utility/keycodes';
export let column;
export let baseTable;
export let formDisplay;
let domEditor;
$: value = formDisplay.getKeyValue(column.columnName);
const applyFilter = () => {
formDisplay.requestKeyValue(column.columnName, domEditor.value);
};
const cancelFilter = () => {
formDisplay.cancelRequestKey();
formDisplay.reload();
};
const handleKeyDown = ev => {
if (ev.keyCode == keycodes.enter) {
applyFilter();
}
if (ev.keyCode == keycodes.escape) {
cancelFilter();
}
};
$: if (domEditor) domEditor.value = value;
</script>
<div class="m-1">
<div class="space-between">
<div>
<FontIcon icon="img primary-key" />
<ColumnLabel {...baseTable.columns.find(x => x.columnName == column.columnName)} />
</div>
{#if formDisplay.config.formViewKeyRequested}
<InlineButton square on:click={cancelFilter}>
<FontIcon icon="icon delete" />
</InlineButton>
{/if}
</div>
<div class="flex">
<input bind:this={domEditor} type="text" on:blur={applyFilter} on:keydown={handleKeyDown} />
</div>
</div>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import FontIcon from '../icons/FontIcon.svelte';
</script>
<div on:click|stopPropagation>
<FontIcon icon="icon form" />
</div>
<style>
div {
position: absolute;
right: 0px;
top: 1px;
color: var(--theme-font-3);
background-color: var(--theme-bg-1);
border: 1px solid var(--theme-bg-1);
}
div:hover {
color: var(--theme-font-hover);
border: var(--theme-border);
top: 1px;
right: 0px;
}
</style>

View File

@@ -38,6 +38,7 @@
export let masterLoadedTime;
export let conid;
export let database;
export let onReferenceSourceChanged;
let isLoadingData = false;
let isLoadedData = false;
@@ -128,6 +129,8 @@
$: former = new ChangeSetFormer(rowData, changeSetState, dispatchChangeSet, formDisplay);
$: if (onReferenceSourceChanged && rowData) onReferenceSourceChanged([rowData], loadedTime);
async function handleConfirmSql(sql) {
const resp = await axiosInstance.request({
url: 'database-connections/query-data',
@@ -158,4 +161,12 @@
}
</script>
<FormView {...$$props} {former} isLoading={isLoadingData} {allRowCount} {rowCountBefore} onSave={handleSave} />
<FormView
{...$$props}
{former}
isLoading={isLoadingData}
{allRowCount}
{rowCountBefore}
onSave={handleSave}
onNavigate={handleNavigate}
/>

View File

@@ -29,3 +29,32 @@ export default function openReferenceForm(rowData, column, conid, database) {
}
);
}
export function openPrimaryKeyForm(rowData, baseTable, conid, database) {
const formViewKey = _.fromPairs(
baseTable.primaryKey.columns.map(({ columnName }) => [columnName, rowData[columnName]])
);
openNewTab(
{
title: baseTable.pureName,
icon: 'img table',
tabComponent: 'TableDataTab',
props: {
schemaName: baseTable.schemaName,
pureName: baseTable.pureName,
conid,
database,
objectTypeField: 'tables',
},
},
{
grid: {
isFormView: true,
formViewKey,
},
},
{
forceNewTab: true,
}
);
}