Files
flipper/desktop/plugins/public/databases/DatabaseDetailSidebar.tsx
Andres Suarez 79023ee190 Update copyright headers from Facebook to Meta
Reviewed By: bhamodi

Differential Revision: D33331422

fbshipit-source-id: 016e8dcc0c0c7f1fc353a348b54fda0d5e2ddc01
2021-12-27 14:31:45 -08:00

217 lines
5.4 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import React, {useMemo, useState, useEffect, useReducer} from 'react';
import {
Panel,
DetailSidebar,
DataInspector as ManagedDataInspector,
theme,
styled,
produce,
Layout,
} from 'flipper-plugin';
import {
Value,
valueToNullableString,
renderValue,
} from './TypeBasedValueRenderer';
import {Button, Input} from 'antd';
type TableRow = {
col: string;
type: Value['type'];
value: React.ReactElement;
};
type DatabaseDetailSidebarProps = {
columnLabels: Array<string>;
columnValues: Array<Value>;
onSave?: ((changes: {[key: string]: string | null}) => void) | undefined;
};
const TableDetailRow = styled.div({
borderBottom: `1px solid ${theme.dividerColor}`,
padding: 8,
});
const TableDetailRowTitle = styled.div({
fontWeight: 'bold',
marginBottom: 8,
});
const TableDetailRowType = styled.span({
color: theme.white,
marginLeft: 8,
fontWeight: 'normal',
});
const TableDetailRowValue = styled.div({});
function sidebarRows(labels: Array<string>, values: Array<Value>): TableRow[] {
return labels.map((label, idx) => buildSidebarRow(label, values[idx]));
}
function buildSidebarRow(key: string, val: Value): TableRow {
let output = renderValue(val, true);
if (
(val.type === 'string' || val.type === 'blob') &&
(val.value[0] === '[' || val.value[0] === '{')
) {
try {
// eslint-disable-next-line
var parsed = JSON.parse(val.value);
} catch (_error) {}
if (parsed) {
output = <ManagedDataInspector data={parsed} expandRoot collapsed />;
}
}
return {
col: key,
type: val.type,
value: output,
};
}
function sidebarEditableRows(
labels: Array<string>,
values: Array<Value>,
rowDispatch: (action: RowAction) => void,
): TableRow[] {
return labels.map((label, idx) =>
buildSidebarEditableRow(label, values[idx], (value: string | null) =>
rowDispatch({type: 'set', key: label, value}),
),
);
}
function buildSidebarEditableRow(
key: string,
val: Value,
onUpdateValue: (value: string | null) => void,
): TableRow {
if (val.type === 'blob' || !val.type) {
return buildSidebarRow(key, val);
}
return {
col: key,
type: val.type,
value: (
<EditField
key={key}
initialValue={valueToNullableString(val)}
onUpdateValue={onUpdateValue}
/>
),
};
}
const EditField = React.memo(
(props: {
initialValue: string | null;
onUpdateValue: (value: string | null) => void;
}) => {
const {initialValue, onUpdateValue} = props;
const [value, setValue] = useState<string | null>(initialValue);
useEffect(() => setValue(initialValue), [initialValue]);
return (
<Input
value={value || ''}
onChange={(e) => {
setValue(e.target.value);
onUpdateValue(e.target.value);
}}
placeholder={value === null ? 'NULL' : undefined}
data-testid={'update-query-input'}
style={{width: '100%'}}
/>
);
},
);
type RowState = {changes: {[key: string]: string | null}; updated: boolean};
type RowAction =
| {type: 'set'; key: string; value: string | null}
| {type: 'reset'};
const rowStateReducer = produce((draftState: RowState, action: RowAction) => {
switch (action.type) {
case 'set':
draftState.changes[action.key] = action.value;
draftState.updated = true;
return;
case 'reset':
draftState.changes = {};
draftState.updated = false;
return;
}
});
export default React.memo(function DatabaseDetailSidebar(
props: DatabaseDetailSidebarProps,
) {
const [editing, setEditing] = useState(false);
const [rowState, rowDispatch] = useReducer(rowStateReducer, {
changes: {},
updated: false,
});
const {columnLabels, columnValues, onSave} = props;
useEffect(() => rowDispatch({type: 'reset'}), [columnLabels, columnValues]);
const rows = useMemo(
() =>
editing
? sidebarEditableRows(columnLabels, columnValues, rowDispatch)
: sidebarRows(columnLabels, columnValues),
[columnLabels, columnValues, editing],
);
return (
<DetailSidebar>
<Panel title="Row details" collapsible>
{onSave ? (
<Layout.Right center>
<div />
{editing ? (
<Layout.Horizontal pad gap>
<Button onClick={() => setEditing(false)}>Close</Button>
<Button
disabled={!rowState.updated}
type="primary"
onClick={() => {
onSave(rowState.changes);
setEditing(false);
}}>
Save
</Button>
</Layout.Horizontal>
) : (
<Layout.Horizontal pad>
<Button onClick={() => setEditing(true)}>Edit</Button>
</Layout.Horizontal>
)}
</Layout.Right>
) : null}
<div>
{rows.map((row) => (
<TableDetailRow key={row.col}>
<TableDetailRowTitle>
{row.col}
<TableDetailRowType>({row.type})</TableDetailRowType>
</TableDetailRowTitle>
<TableDetailRowValue>{row.value}</TableDetailRowValue>
</TableDetailRow>
))}
</div>
</Panel>
</DetailSidebar>
);
});