UI tweaks for the query history.

This commit is contained in:
Matthew Kleiman 2017-07-06 13:08:29 +01:00 committed by Dave Page
parent 1291841d98
commit e2cbaaef71
20 changed files with 380 additions and 300 deletions

View File

@ -10,44 +10,17 @@
import React from 'react';
import Shapes from '../../react_shapes';
import NonSelectableElementStyle from '../../styles/non_selectable';
import MessageHeaderStyle from '../../styles/header_label';
const containerStyle = {
flex: '2 2 0%',
flexDirection: 'column',
display: 'flex',
};
const messageContainerStyle = {
flex: '0 1 auto',
overflow: 'auto',
position: 'relative',
height: '100%',
};
const errorMessageStyle = {
border: '0',
paddingLeft: '0',
backgroundColor: '#ffffff',
fontSize: '13px',
position: 'absolute',
};
const messageLabelStyle = _.extend({flex: '0 0 auto'},
MessageHeaderStyle,
NonSelectableElementStyle);
export default class HistoryDetailMessage extends React.Component {
render() {
return (
<div style={containerStyle}>
<div style={messageLabelStyle}>
<div className='message'>
<div className='message-header'>
Messages
</div>
<div style={messageContainerStyle}>
<pre style={errorMessageStyle}>
<div className='content'>
<pre className='content-value'>
{this.props.historyEntry.message}
</pre>
</div>

View File

@ -10,18 +10,6 @@
import React from 'react';
import moment from 'moment';
import Shapes from '../../react_shapes';
import HeaderDescriptionStyle from '../../styles/header_label';
const queryMetaDataStyle = {
flex: 1,
};
const headerStyle = {
display: 'flex',
};
const headerValueStyle = {
display: 'block',
fontSize: '14px',
};
export default class HistoryDetailMetadata extends React.Component {
@ -30,18 +18,18 @@ export default class HistoryDetailMetadata extends React.Component {
}
queryMetaData(data, description) {
return <div style={queryMetaDataStyle}>
<span style={headerValueStyle}>
return <div className='item'>
<span className='value'>
{data}
</span>
<span style={HeaderDescriptionStyle}>
<span className='description'>
{description}
</span>
</div>;
}
render() {
return <div style={headerStyle}>
return <div className='metadata'>
{this.queryMetaData(this.formatDate(this.props.historyEntry.start_time), 'Date')}
{this.queryMetaData(this.props.historyEntry.row_affected.toLocaleString(), 'Rows Affected')}
{this.queryMetaData(this.props.historyEntry.total_time, 'Duration')}

View File

@ -1,20 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
import update from 'immutability-helper';
import {errorStyle} from '../../styles/history_entry_styles';
export default class QueryHistoryErrorEntry extends QueryHistoryVanillaEntry {
componentWillMount() {
this.setState({
outerDivStyle: update(this.state.outerDivStyle, {$merge: errorStyle}),
});
}
}

View File

@ -1,21 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
import update from 'immutability-helper';
import {selectedFontStyle, selectedOuterStyle} from '../../styles/history_entry_styles';
export default class QueryHistorySelectedEntry extends QueryHistoryVanillaEntry {
componentWillMount() {
this.setState({
outerDivStyle: update(this.state.outerDivStyle, {$merge: selectedOuterStyle}),
secondLineStyle: update(this.state.secondLineStyle, {$merge: selectedFontStyle}),
});
}
}

View File

@ -1,22 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
import update from 'immutability-helper';
import {selectedFontStyle, selectedOuterStyle, selectedErrorBgColor} from '../../styles/history_entry_styles';
export default class QueryHistorySelectedErrorEntry extends QueryHistoryVanillaEntry {
componentWillMount() {
let selectedErrorStyle = update(selectedOuterStyle, {$merge: selectedErrorBgColor});
this.setState({
outerDivStyle: update(this.state.outerDivStyle, {$merge: selectedErrorStyle}),
secondLineStyle: update(this.state.secondLineStyle, {$merge: selectedFontStyle}),
});
}
}

View File

@ -1,47 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import moment from 'moment';
import Shapes from '../../react_shapes';
import {plainOuterDivStyle, plainSecondLineStyle, sqlStyle, timestampStyle} from '../../styles/history_entry_styles';
export default class QueryHistoryVanillaEntry extends React.Component {
formatDate(date) {
return (moment(date).format('MMM D YYYY [] HH:mm:ss'));
}
constructor(props) {
super(props);
this.state = {
outerDivStyle: plainOuterDivStyle,
secondLineStyle: plainSecondLineStyle,
};
}
render() {
return (
<div style={this.state.outerDivStyle}>
<div style={sqlStyle}>
{this.props.historyEntry.query}
</div>
<div style={this.state.secondLineStyle}>
<div style={timestampStyle}>
{this.formatDate(this.props.historyEntry.start_time)}
</div>
</div>
</div>
);
}
}
QueryHistoryVanillaEntry.propTypes = {
historyEntry: Shapes.historyDetail,
isSelected: React.PropTypes.bool,
};

View File

@ -19,9 +19,6 @@ const queryEntryListDivStyle = {
const queryDetailDivStyle = {
display: 'flex',
};
const liStyle = {
borderBottom: '1px solid #cccccc',
};
export default class QueryHistory extends React.Component {
@ -75,11 +72,11 @@ export default class QueryHistory extends React.Component {
return (
<SplitPane defaultSize="50%" split="vertical" pane1Style={queryEntryListDivStyle}
pane2Style={queryDetailDivStyle}>
<div id='query_list'>
<div id='query_list' className="query-history">
<ul>
{this.retrieveOrderedHistory()
.map((entry, index) =>
<li key={index} style={liStyle} onClick={this.onClickHandler.bind(this, index)}>
<li key={index} className='list-item' onClick={this.onClickHandler.bind(this, index)}>
<QueryHistoryEntry historyEntry={entry} isSelected={index == this.state.selectedEntry}/>
</li>)
.value()

View File

@ -13,54 +13,22 @@ import HistoryDetailQuery from './detail/history_detail_query';
import HistoryDetailMessage from './detail/history_detail_message';
import Shapes from '../react_shapes';
const outerStyle = {
width: '100%',
paddingTop: '10px',
display: 'flex',
flexDirection: 'column',
};
const detailVerticalTop = {
flex: 1,
padding: '0 10px',
};
const detailVerticalMiddle = {
flex: 5,
marginLeft: '10px',
marginRight: '10px',
height: 0,
position: 'relative',
};
const hrStyle = {
borderColor: '#cccccc',
marginTop: '11px',
marginBottom: '8px',
};
const detailVerticalBottom = {
flex: 2,
display: 'flex',
paddingLeft: '10px',
};
export default class QueryHistoryDetail extends React.Component {
render() {
if (!_.isUndefined(this.props.historyEntry)) {
return (
<div id='query_detail' style={outerStyle}>
<div style={detailVerticalTop}>
<div id='query_detail' className='query-detail'>
<div className='metadata-block'>
<HistoryDetailMetadata {...this.props} />
</div>
<div style={detailVerticalMiddle}>
<div className='query-statement-block'>
<HistoryDetailQuery {...this.props}/>
</div>
<div>
<hr style={hrStyle}/>
<hr className='block-divider'/>
</div>
<div style={detailVerticalBottom}>
<div className='message-block'>
<HistoryDetailMessage {...this.props}/>
</div>
</div>);

View File

@ -9,24 +9,40 @@
import React from 'react';
import Shapes from '../react_shapes';
import QueryHistoryErrorEntry from './entry/query_history_error_entry';
import QueryHistorySelectedErrorEntry from './entry/query_history_selected_error_entry';
import QueryHistorySelectedEntry from './entry/query_history_selected_entry';
import QueryHistoryVanillaEntry from './entry/query_history_vanilla_entry';
import moment from 'moment';
export default class QueryHistoryEntry extends React.Component {
formatDate(date) {
return (moment(date).format('MMM D YYYY [] HH:mm:ss'));
}
renderWithClasses(outerDivStyle) {
return (
<div className={'entry ' + outerDivStyle}>
<div className='query'>
{this.props.historyEntry.query}
</div>
<div className='other-info'>
<div className='timestamp'>
{this.formatDate(this.props.historyEntry.start_time)}
</div>
</div>
</div>
);
}
render() {
if (this.hasError()) {
if (this.props.isSelected) {
return <QueryHistorySelectedErrorEntry {...this.props}/>;
return this.renderWithClasses('error selected');
} else {
return <QueryHistoryErrorEntry {...this.props}/>;
return this.renderWithClasses('error');
}
} else {
if (this.props.isSelected) {
return <QueryHistorySelectedEntry {...this.props}/>;
return this.renderWithClasses('selected');
} else {
return <QueryHistoryVanillaEntry {...this.props}/>;
return this.renderWithClasses('');
}
}
}

View File

@ -1,14 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
export default {
display: 'block',
fontSize: '12px',
color: '#888888',
};

View File

@ -1,57 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import update from 'immutability-helper';
export const plainOuterDivStyle = {
paddingLeft: '8px',
paddingRight: '18px',
paddingTop: '-2px',
paddingBottom: '-2px',
fontFamily: 'monospace',
fontSize: '14px',
backgroundColor: '#FFF',
border: '2px solid transparent',
marginLeft: '1px',
};
export const sqlStyle = {
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
userSelect: 'auto',
};
export const plainSecondLineStyle = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
fontSize: '13px',
color: '#888888',
};
export const timestampStyle = {
alignSelf: 'flex-start',
};
export const selectedFontStyle = {
color: '#2c76b4',
fontWeight: 'bold',
};
export const selectedOuterStyle = update(selectedFontStyle, {
$merge: {
border: '2px solid #2c76b4',
backgroundColor: '#e7f2ff',
},
});
export const errorStyle = {backgroundColor: '#F7D0D5'};
export const selectedErrorBgColor = {backgroundColor: '#DCC4D1'};

View File

@ -1,18 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
export default {
WebkitTouchCallout: 'none',
WebkitUserSelect: 'none',
KhtmlUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
userSelect: 'none',
cursor: 'default',
};

View File

@ -0,0 +1,126 @@
/*doc
---
title: Grays
name: Grays
category: colors
---
For text, avoid using black or #000 to lower the contrast between the background and text.
```html_example
<div class="row">
<div class="row">
<div class="col-xs-6 col-md-3">
<div class="color-chip bg-gray-1">
#f9f9f9
</div>
</div>
<div class="col-xs-6 col-md-3">
<div class="color-chip bg-gray-2">
#e8e8e8
</div>
</div>
<div class="col-xs-6 col-md-3">
<div class="color-chip bg-gray-3">
#cccccc
</div>
</div>
<div class="col-xs-6 col-md-3">
<div class="color-chip bg-gray-4">
#888888
</div>
</div>
<div class="col-xs-6 col-md-3">
<div class="color-chip bg-gray-5 font-white">
#555555
</div>
</div>
<div class="col-xs-6 col-md-3">
<div class="color-chip bg-gray-6 font-white">
#333333
</div>
</div>
</div>
</div>
```
*/
.color-chip {
align-items: center;
border-radius: 3px;
box-shadow: inset 0 -3px 0 0 rgba(0, 0, 0, .15);
color: rgba(0, 0, 0, .65);
display: flex;
font-size: 1.25em;
height: 100px;
justify-content: center;
margin: 0 0 1em;
width: 100%;
}
$color-gray-1: #f9f9f9;
$color-gray-2: #e8e8e8;
$color-gray-3: #cccccc;
$color-gray-4: #888888;
$color-gray-5: #555555;
$color-gray-6: #333333;
.bg-gray-1 {
background-color: $color-gray-1;
}
.bg-gray-2 {
background-color: $color-gray-2;
}
.bg-gray-3 {
background-color: $color-gray-3;
}
.bg-gray-4 {
background-color: $color-gray-4;
}
.bg-gray-5 {
background-color: $color-gray-5;
}
.bg-gray-6 {
background-color: $color-gray-6;
}
.border-gray-1 {
border-color: $color-gray-1;
}
.border-gray-2 {
border-color: $color-gray-2;
}
.border-gray-3 {
border-color: $color-gray-3;
}
.border-gray-4 {
border-color: $color-gray-4;
}
.border-gray-5 {
border-color: $color-gray-5;
}
.border-gray-6 {
border-color: $color-gray-6;
}
.font-gray-4 {
color: $color-gray-4;
}
.font-gray-6 {
color: $color-gray-6;
}
.font-white {
color: #FFFFFF;
}

View File

@ -79,6 +79,10 @@ These colors should be used to highlight hover options in dropdown menus and cat
width: 100%;
}
.bg-white-1 {
background-color: #ffffff;
}
.bg-blue-1 {
background-color: #e7f2ff;
}

View File

@ -0,0 +1,49 @@
/*doc
---
title: Primary blue
name: colors-primaryblue
category: colors
---
This color should be used to call attention to the main part of the app. Use sparingly.
```html_example
<div class="row">
<div class="row">
<div class="col-xs-6 col-md-3">
<div class="color-chip bg-primary-blue font-white">
#2c76b4
</div>
</div>
</div>
</div>
```
*/
.color-chip {
align-items: center;
border-radius: 3px;
box-shadow: inset 0 -3px 0 0 rgba(0, 0, 0, .15);
color: rgba(0, 0, 0, .65);
display: flex;
font-size: 1.25em;
height: 100px;
justify-content: center;
margin: 0 0 1em;
width: 100%;
}
$primary-blue: #2c76b4;
.bg-primary-blue {
background-color: $primary-blue;
}
.border-primary-blue {
border-color: $primary-blue;
}
.font-primary-blue {
color: $primary-blue;
}

View File

@ -0,0 +1,9 @@
.not-selectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
ms-user-select: none;
user-select: none;
cursor: default;
}

View File

@ -1,6 +1,12 @@
$enable-flex: false;
@import 'alert';
@import 'primaryblue';
@import 'colorsgrey';
@import 'othercolors';
@import 'typography';
@import 'utils';
@import 'alertify.overrides';
@import 'sqleditor/history';

View File

@ -0,0 +1,139 @@
.query-history {
.list-item {
border-bottom: 1px solid $color-gray-3;
}
.entry {
@extend .text-14;
@extend .bg-white-1;
padding: -2px 18px -2px 8px;
font-family: monospace;
border: 2px solid transparent;
margin-left: 1px;
.other-info{
@extend .text-13;
@extend .font-gray-4;
font-family: monospace;
display: flex;
flex-direction: row;
justify-content: space-between;
.timestamp {
align-self: flex-start;
}
}
.query {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
user-select: initial;
}
}
.entry.error {
@extend .bg-red-1;
}
.entry.selected.error {
background-color: #d7d0d9;
}
.entry.selected {
@extend .font-primary-blue;
@extend .border-primary-blue;
@extend .bg-blue-1;
font-weight: bold;
border: 2px solid;
.other-info{
@extend .font-primary-blue;
font-weight: bold;
}
}
}
.header-label {
@extend .text-12;
@extend .font-gray-4;
display: block;
}
.query-detail {
width: 100%;
padding-top: 10px;
display: flex;
flex-direction: column;
.metadata-block {
flex: 1;
padding: 0 10px;
.metadata {
display: flex;
.item {
flex: 1;
.value {
@extend .text-14;
display: block;
}
.description {
@extend .header-label;
}
}
}
}
.query-statement-block {
flex: 5;
margin-left: 10px;
margin-right: 10px;
height: 0;
position: relative;
}
.block-divider {
@extend .border-gray-3;
margin-top: 11px;
margin-bottom: 8px;
}
.message-block {
flex: 2;
display: flex;
padding-left: 10px;
.message {
flex: 2 2 0%;
flex-direction: column;
display: flex;
.message-header {
@extend .header-label;
@extend .not-selectable;
flex: 0 0 auto;
}
.content {
flex: 0 1 auto;
overflow: auto;
position: relative;
height: 100%;
.content-value {
@extend .bg-white-1;
@extend .text-13;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
border: 0;
padding-left: 0;
position: absolute;
}
}
}
}
}

View File

@ -118,6 +118,7 @@
.sql-editor-history-container {
height: 100%;
overflow: auto;
border-top: 1px solid #cccccc;
}
.sql-status-cell {

View File

@ -94,12 +94,13 @@ describe('QueryHistory', () => {
it('renders the most recent query as selected', () => {
expect(foundChildren.at(0).nodes.length).toBe(1);
expect(foundChildren.at(0).find('QueryHistorySelectedEntry').length).toBe(1);
expect(foundChildren.at(0).hasClass('selected')).toBeTruthy();
});
it('renders the older query as not selected', () => {
expect(foundChildren.at(1).nodes.length).toBe(1);
expect(foundChildren.at(1).find('QueryHistoryErrorEntry').length).toBe(1);
expect(foundChildren.at(1).hasClass('selected')).toBeFalsy();
expect(foundChildren.at(1).hasClass('error')).toBeTruthy();
});
});
@ -143,12 +144,14 @@ describe('QueryHistory', () => {
it('renders the most recent query as selected in the left pane', () => {
expect(foundChildren.at(0).nodes.length).toBe(1);
expect(foundChildren.at(0).find('QueryHistoryVanillaEntry').length).toBe(1);
expect(foundChildren.at(0).hasClass('selected')).toBeFalsy();
expect(foundChildren.at(0).hasClass('error')).toBeFalsy();
});
it('renders the older query as selected in the left pane', () => {
expect(foundChildren.at(1).nodes.length).toBe(1);
expect(foundChildren.at(1).find('QueryHistorySelectedErrorEntry').length).toBe(1);
expect(foundChildren.at(1).hasClass('selected')).toBeTruthy();
expect(foundChildren.at(1).hasClass('error')).toBeTruthy();
});
});