form view

This commit is contained in:
Jan Prochazka
2021-03-20 20:22:49 +01:00
parent 5e59926556
commit 10c77ad153
12 changed files with 575 additions and 16 deletions

View File

@@ -7,11 +7,15 @@
export let config;
export let gridCoreComponent;
export let formViewComponent;
export let formDisplay;
export let isDetailView = false;
export let showReferences = false;
let managerSize;
$: isFormView = !!(formDisplay && formDisplay.config && formDisplay.config.isFormView);
</script>
<HorizontalSplitter initialValue="300px" bind:size={managerSize}>
@@ -33,7 +37,15 @@
</WidgetColumnBar>
</div>
<svelte:fragment slot="2">
<svelte:component this={gridCoreComponent} {...$$props} />
{#if isFormView}
<svelte:component this={formViewComponent} {...$$props} />
{:else}
<svelte:component
this={gridCoreComponent}
{...$$props}
formViewAvailable={!!formViewComponent && !!formDisplay}
/>
{/if}
</svelte:fragment>
</HorizontalSplitter>

View File

@@ -23,6 +23,7 @@
export let rowIndex;
export let col;
export let rowData;
export let colIndex = undefined;
export let hintFieldsAllowed = undefined;
export let isSelected = false;
@@ -39,7 +40,7 @@
<td
data-row={rowIndex}
data-col={col.colIndex}
data-col={colIndex == null ? col.colIndex : colIndex}
class:isSelected
class:isFrameSelected
class:isModifiedRow

View File

@@ -118,6 +118,15 @@
onClick: () => getCurrentDataGrid().exportGrid(),
});
registerCommand({
id: 'dataGrid.switchToForm',
category: 'Data grid',
name: 'Switch to form',
keyText: 'F4',
testEnabled: () => getCurrentDataGrid()?.formViewEnabled(),
onClick: () => getCurrentDataGrid().switchToForm(),
});
function getRowCountInfo(selectedCells, grider, realColumnUniqueNames, selectedRowData, allRowCount) {
if (selectedCells.length > 1 && selectedCells.every(x => _.isNumber(x[0]) && _.isNumber(x[1]))) {
let sum = _.sumBy(selectedCells, cell => {
@@ -193,6 +202,7 @@
export let onSave;
export let focusOnVisible = false;
export let onExportGrid = null;
export let formViewAvailable = false;
export let isLoadedAll;
export let loadedTime;
@@ -329,6 +339,16 @@
}
}
export function formViewEnabled() {
return formViewAvailable && display.baseTable && display.baseTable.primaryKey;
}
export function switchToForm() {
const cell = currentCell;
const rowData = isRegularCell(cell) ? grider.getRowData(cell[0]) : null;
display.switchToFormView(rowData);
}
// export function getGeneralAllowSave() {
// return generalAllowSave;
// }
@@ -869,6 +889,7 @@
{ command: 'dataGrid.refresh' },
{ command: 'dataGrid.copyToClipboard' },
{ command: 'dataGrid.export' },
{ command: 'dataGrid.switchToForm' },
{ divider: true },
{ command: 'dataGrid.save' },
{ command: 'dataGrid.revertRowChanges' },

View File

@@ -13,6 +13,7 @@
import DataGrid from './DataGrid.svelte';
import ReferenceHeader from './ReferenceHeader.svelte';
import SqlDataGridCore from './SqlDataGridCore.svelte';
import SqlFormView from '../formview/SqlFormView.svelte';
export let conid;
export let database;
@@ -44,16 +45,19 @@
setCache,
$dbinfo
)
: // ? new TableFormViewDisplay(
// { schemaName, pureName },
// findEngineDriver(connection, $extensions),
// $config,
// config.update,
// $cache,
// cache.update,
// $dbinfo
// )
null;
: null;
$: formDisplay = connection
? new TableFormViewDisplay(
{ schemaName, pureName },
findEngineDriver($connection, $extensions),
config,
setConfig,
cache,
setCache,
$dbinfo
)
: null;
const setChildConfig = (value, reference = undefined) => {
if (_.isFunction(value)) {
@@ -111,7 +115,9 @@
<DataGrid
{...$$props}
gridCoreComponent={SqlDataGridCore}
formViewComponent={SqlFormView}
{display}
{formDisplay}
showReferences
onReferenceSourceChanged={reference ? handleReferenceSourceChanged : null}
onReferenceClick={value => {

View File

@@ -0,0 +1,94 @@
import {
ChangeSet,
changeSetContainsChanges,
changeSetInsertNewRow,
createChangeSet,
deleteChangeSetRows,
findExistingChangeSetItem,
getChangeSetInsertedRows,
TableFormViewDisplay,
revertChangeSetRowChanges,
setChangeSetValue,
ChangeSetRowDefinition,
} from 'dbgate-datalib';
import Former from './Former';
export default class ChangeSetFormer extends Former {
public changeSet: ChangeSet;
public setChangeSet: Function;
private batchChangeSet: ChangeSet;
public rowDefinition: ChangeSetRowDefinition;
public rowStatus;
public rowData: {};
constructor(
public sourceRow: any,
public changeSetState,
public dispatchChangeSet,
public display: TableFormViewDisplay
) {
super();
this.changeSet = changeSetState && changeSetState.value;
this.setChangeSet = value => dispatchChangeSet({ type: 'set', value });
this.batchChangeSet = null;
this.rowDefinition = display.getChangeSetRow(sourceRow);
const [matchedField, matchedChangeSetItem] = findExistingChangeSetItem(this.changeSet, this.rowDefinition);
this.rowData = matchedChangeSetItem ? { ...sourceRow, ...matchedChangeSetItem.fields } : sourceRow;
let status = 'regular';
if (matchedChangeSetItem && matchedField == 'updates') status = 'updated';
if (matchedField == 'deletes') status = 'deleted';
this.rowStatus = {
status,
modifiedFields:
matchedChangeSetItem && matchedChangeSetItem.fields ? new Set(Object.keys(matchedChangeSetItem.fields)) : null,
};
}
applyModification(changeSetReducer) {
if (this.batchChangeSet) {
this.batchChangeSet = changeSetReducer(this.batchChangeSet);
} else {
this.setChangeSet(changeSetReducer(this.changeSet));
}
}
setCellValue(uniqueName: string, value: any) {
const row = this.sourceRow;
const definition = this.display.getChangeSetField(row, uniqueName);
this.applyModification(chs => setChangeSetValue(chs, definition, value));
}
deleteRow(index: number) {
this.applyModification(chs => deleteChangeSetRows(chs, this.rowDefinition));
}
beginUpdate() {
this.batchChangeSet = this.changeSet;
}
endUpdate() {
this.setChangeSet(this.batchChangeSet);
this.batchChangeSet = null;
}
revertRowChanges() {
this.applyModification(chs => revertChangeSetRowChanges(chs, this.rowDefinition));
}
revertAllChanges() {
this.applyModification(chs => createChangeSet());
}
undo() {
this.dispatchChangeSet({ type: 'undo' });
}
redo() {
this.dispatchChangeSet({ type: 'redo' });
}
get canUndo() {
return this.changeSetState.canUndo;
}
get canRedo() {
return this.changeSetState.canRedo;
}
get containsChanges() {
return changeSetContainsChanges(this.changeSet);
}
}

View File

@@ -0,0 +1,171 @@
<script lang="ts" context="module">
let lastFocusedFormView = null;
const getCurrentDataForm = () =>
lastFocusedFormView?.getTabId && lastFocusedFormView?.getTabId() == getActiveTabId() ? lastFocusedFormView : null;
registerCommand({
id: 'dataForm.switchToTable',
category: 'Data form',
name: 'Switch to table',
keyText: 'F4',
testEnabled: () => getCurrentDataForm() != null,
onClick: () => getCurrentDataForm().switchToTable(),
});
</script>
<script lang="ts">
import _ from 'lodash';
import { getContext } from 'svelte';
import { get_current_component } from 'svelte/internal';
import invalidateCommands from '../commands/invalidateCommands';
import registerCommand from '../commands/registerCommand';
import DataGridCell from '../datagrid/DataGridCell.svelte';
import ColumnLabel from '../elements/ColumnLabel.svelte';
import { getActiveTabId } from '../stores';
import contextMenu from '../utility/contextMenu';
export let config;
export let setConfig;
export let focusOnVisible = false;
export let allRowCount;
export let rowCountBefore;
export let isLoading;
export let former;
export let formDisplay;
let wrapperHeight = 1;
let rowHeight = 1;
let currentCell = [0, 0];
const instance = get_current_component();
const tabid = getContext('tabid');
const tabVisible: any = getContext('tabVisible');
let domFocusField;
$: if ($tabVisible && domFocusField && focusOnVisible) {
domFocusField.focus();
}
$: rowData = former?.rowData;
$: rowStatus = former?.rowStatus;
$: rowCount = Math.floor((wrapperHeight - 20) / rowHeight);
$: columnChunks = _.chunk(formDisplay.columns, rowCount) as any[][];
export function getTabId() {
return tabid;
}
export function switchToTable() {
setConfig(cfg => ({
...cfg,
isFormView: false,
formViewKey: null,
}));
}
function createMenu() {
return [{ command: 'dataForm.switchToTable' }];
}
</script>
<div class="outer">
<div class="wrapper" use:contextMenu={createMenu} bind:clientHeight={wrapperHeight}>
{#each columnChunks as chunk, chunkIndex}
<table>
{#each chunk as col, rowIndex}
<tr>
{#if chunkIndex == 0 && rowIndex == 0}
<td
class="header-cell"
data-row={rowIndex}
data-col={chunkIndex * 2}
bind:clientHeight={rowHeight}
style={`height: ${rowHeight}px`}
>
<ColumnLabel {...col} headerText={col.columnName} />
</td>
{:else}
<td class="header-cell" data-row={rowIndex} data-col={chunkIndex * 2} style={`height: ${rowHeight}px`}>
<ColumnLabel {...col} headerText={col.columnName} />
</td>
{/if}
<DataGridCell
{rowIndex}
{col}
{rowData}
colIndex={chunkIndex * 2 + 1}
isSelected={currentCell[0] == rowIndex && currentCell[1] == chunkIndex * 2 + 1}
isModifiedCell={rowStatus.modifiedFields && rowStatus.modifiedFields.has(col.uniqueName)}
/>
</tr>
{/each}
</table>
{/each}
<input
type="text"
class="focus-field"
bind:this={domFocusField}
on:focus={() => {
lastFocusedFormView = instance;
invalidateCommands();
}}
/>
</div>
</div>
<style>
table {
border-collapse: collapse;
outline: none;
}
.outer {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
}
.wrapper {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
display: flex;
overflow-x: scroll;
}
tr {
background-color: var(--theme-bg-0);
}
tr:nth-child(6n + 3) {
background-color: var(--theme-bg-1);
}
tr:nth-child(6n + 6) {
background-color: var(--theme-bg-alt);
}
.header-cell {
border: 1px solid var(--theme-border);
text-align: left;
padding: 0;
margin: 0;
background-color: var(--theme-bg-1);
overflow: hidden;
}
.focus-field {
position: absolute;
left: -1000px;
top: -1000px;
}
</style>

View File

@@ -0,0 +1,53 @@
// export interface GriderRowStatus {
// status: 'regular' | 'updated' | 'deleted' | 'inserted';
// modifiedFields?: Set<string>;
// insertedFields?: Set<string>;
// deletedFields?: Set<string>;
// }
export default abstract class Former {
public rowData: any;
// getRowStatus(index): GriderRowStatus {
// const res: GriderRowStatus = {
// status: 'regular',
// };
// return res;
// }
beginUpdate() {}
endUpdate() {}
setCellValue(uniqueName: string, value: any) {}
revertRowChanges() {}
revertAllChanges() {}
undo() {}
redo() {}
get editable() {
return false;
}
get canInsert() {
return false;
}
get allowSave() {
return this.containsChanges;
}
get canUndo() {
return false;
}
get canRedo() {
return false;
}
get containsChanges() {
return false;
}
get disableLoadNextPage() {
return false;
}
get errors() {
return null;
}
updateRow(changeObject) {
for (const key of Object.keys(changeObject)) {
this.setCellValue(key, changeObject[key]);
}
}
}

View File

@@ -0,0 +1,109 @@
<script lang="ts" context="module">
async function loadRow(props, sql) {
const { conid, database } = props;
if (!sql) return null;
const response = await axiosInstance.request({
url: 'database-connections/query-data',
method: 'post',
params: {
conid,
database,
},
data: { sql },
});
if (response.data.errorMessage) return response.data;
return response.data.rows[0];
}
</script>
<script lang="ts">
import axiosInstance from '../utility/axiosInstance';
import ChangeSetFormer from './ChangeSetFormer';
import FormView from './FormView.svelte';
export let formDisplay;
export let changeSetState;
export let dispatchChangeSet;
let isLoadingData = false;
let isLoadedData = false;
let rowData = null;
let isLoadingCount = false;
let isLoadedCount = false;
let loadedTime = new Date().getTime();
let allRowCount = null;
let rowCountBefore = null;
let errorMessage = null;
const handleLoadCurrentRow = async () => {
if (isLoadingData) return;
let newLoadedRow = false;
if (formDisplay.config.formViewKeyRequested || formDisplay.config.formViewKey) {
isLoadingData = true;
const row = await loadRow($$props, formDisplay.getCurrentRowQuery());
isLoadingData = false;
isLoadedData = true;
rowData = row;
loadedTime = new Date().getTime();
newLoadedRow = row;
}
if (formDisplay.config.formViewKeyRequested && newLoadedRow) {
formDisplay.cancelRequestKey(newLoadedRow);
}
if (!newLoadedRow && !formDisplay.config.formViewKeyRequested) {
await handleNavigate('first');
}
};
const handleLoadRowCount = async () => {
isLoadingCount = true;
const countRow = await loadRow($$props, formDisplay.getCountQuery());
const countBeforeRow = await loadRow($$props, formDisplay.getBeforeCountQuery());
isLoadedCount = true;
isLoadingCount = false;
allRowCount = countRow ? parseInt(countRow.count) : null;
rowCountBefore = countBeforeRow ? parseInt(countBeforeRow.count) : null;
};
const handleNavigate = async command => {
isLoadingData = true;
const row = await loadRow($$props, formDisplay.navigateRowQuery(command));
if (row) {
formDisplay.navigate(row);
}
isLoadingData = false;
isLoadedData = true;
isLoadedCount = false;
allRowCount = null;
rowCountBefore = null;
rowData = row;
loadedTime = new Date().getTime();
};
export function reload() {
isLoadingData = false;
isLoadedData = false;
isLoadingCount = false;
isLoadedCount = false;
rowData = null;
loadedTime = new Date().getTime();
allRowCount = null;
rowCountBefore = null;
errorMessage = null;
}
$: {
if (formDisplay.isLoadedCorrectly) {
if (!isLoadedData && !isLoadingData) handleLoadCurrentRow();
if (isLoadedData && !isLoadingCount && !isLoadedCount) handleLoadRowCount();
}
}
$: former = new ChangeSetFormer(rowData, changeSetState, dispatchChangeSet, formDisplay);
</script>
<FormView {...$$props} {former} isLoading={isLoadingData} {allRowCount} {rowCountBefore} />

View File

@@ -0,0 +1,31 @@
import _ from 'lodash';
import openNewTab from '../utility/openNewTab';
export default function openReferenceForm(rowData, column, conid, database) {
const formViewKey = _.fromPairs(
column.foreignKey.columns.map(({ refColumnName, columnName }) => [refColumnName, rowData[columnName]])
);
openNewTab(
{
title: column.foreignKey.refTableName,
icon: 'img table',
tabComponent: 'TableDataTab',
props: {
schemaName: column.foreignKey.refSchemaName,
pureName: column.foreignKey.refTableName,
conid,
database,
objectTypeField: 'tables',
},
},
{
grid: {
isFormView: true,
formViewKey,
},
},
{
forceNewTab: true,
}
);
}

View File

@@ -35,8 +35,10 @@
import AceEditor from '../query/AceEditor.svelte';
import useEditorData from '../query/useEditorData';
import { getActiveTabId } from '../stores';
import { getActiveTabId, openedTabs } from '../stores';
import invalidateCommands from '../commands/invalidateCommands';
import openNewTab from '../utility/openNewTab';
import { setSelectedTab } from '../utility/common';
export let tabid;
@@ -70,7 +72,25 @@
return tabid;
}
const { editorState, editorValue, setEditorData } = useEditorData({ tabid });
export async function preview() {
await saveToStorage();
const existing = ($openedTabs || []).find(x => x.props && x.props.sourceTabId == tabid && x.closedTime == null);
if (existing) {
setSelectedTab(existing.tabid);
} else {
const thisTab = ($openedTabs || []).find(x => x.tabid == tabid);
openNewTab({
title: thisTab.title,
icon: 'img preview',
tabComponent: 'MarkdownPreviewTab',
props: {
sourceTabId: tabid,
},
});
}
}
const { editorState, editorValue, setEditorData, saveToStorage } = useEditorData({ tabid });
function createMenu() {
return [

View File

@@ -0,0 +1,41 @@
<script lang="ts">
import { getContext } from 'svelte';
import LoadingInfo from '../elements/LoadingInfo.svelte';
import Markdown from '../elements/Markdown.svelte';
import useEditorData from '../query/useEditorData';
export let sourceTabId;
const tabVisible: any = getContext('tabVisible');
let data = null;
const { initialLoad, editorState } = useEditorData({
tabid: sourceTabId,
onInitialData: value => {
console.log('onInitialData', value);
data = value;
},
});
$: if ($tabVisible) initialLoad();
</script>
{#if $editorState.isLoading}
<div>
<LoadingInfo message="Loading markdown page" />
</div>
{:else}
<div>
{#key data}
<Markdown source={data || ''} />
{/key}
</div>
{/if}
<style>
div {
padding: 10px;
overflow: auto;
flex: 1;
}
</style>

View File

@@ -9,7 +9,7 @@ import * as PluginTab from './PluginTab.svelte';
import * as ChartTab from './ChartTab.svelte';
import * as MarkdownEditorTab from './MarkdownEditorTab.svelte';
import * as MarkdownViewTab from './MarkdownViewTab.svelte';
// import MarkdownPreviewTab from './MarkdownPreviewTab';
import * as MarkdownPreviewTab from './MarkdownPreviewTab.svelte';
import * as FavoriteEditorTab from './FavoriteEditorTab.svelte';
import * as QueryDesignTab from './QueryDesignTab.svelte';
@@ -25,7 +25,7 @@ export default {
ChartTab,
MarkdownEditorTab,
MarkdownViewTab,
// MarkdownPreviewTab,
MarkdownPreviewTab,
FavoriteEditorTab,
QueryDesignTab,
};