mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 10:11:03 +00:00
Extracted footer into own component, moved pause into icon
This commit is contained in:
@@ -2,19 +2,17 @@ import React from 'react';
|
||||
|
||||
import Logo from './logo';
|
||||
import AppStore from '../stores/app-store';
|
||||
import Footer from './footer.js';
|
||||
import Sidebar from './sidebar.js';
|
||||
import Status from './status.js';
|
||||
import Topologies from './topologies.js';
|
||||
import TopologyOptions from './topology-options.js';
|
||||
import { getUpdateBufferSize } from '../utils/update-buffer-utils';
|
||||
import { contrastModeUrl, isContrastMode } from '../utils/contrast-utils';
|
||||
import { getApiDetails, getTopologies, basePathSlash } from '../utils/web-api-utils';
|
||||
import { clickDownloadGraph, clickForceRelayout, clickPauseUpdate, clickResumeUpdate, hitEsc } from '../actions/app-actions';
|
||||
import { getApiDetails, getTopologies } from '../utils/web-api-utils';
|
||||
import { hitEsc } from '../actions/app-actions';
|
||||
import Details from './details';
|
||||
import Nodes from './nodes';
|
||||
import EmbeddedTerminal from './embedded-terminal';
|
||||
import { getRouter } from '../utils/router-utils';
|
||||
import { formatDate } from '../utils/string-utils';
|
||||
import { showingDebugToolbar, DebugToolbar } from './debug-toolbar.js';
|
||||
|
||||
const ESC_KEY_CODE = 27;
|
||||
@@ -76,25 +74,12 @@ export default class App extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {nodeDetails, updatePaused, updatePausedAt, controlPipe } = this.state;
|
||||
const {nodeDetails, controlPipe } = this.state;
|
||||
const showingDetails = nodeDetails.size > 0;
|
||||
const showingTerminal = controlPipe;
|
||||
// width of details panel blocking a view
|
||||
const detailsWidth = showingDetails ? 450 : 0;
|
||||
const topMargin = 100;
|
||||
const contrastMode = isContrastMode();
|
||||
// link url to switch contrast with current UI state
|
||||
const otherContrastModeUrl = contrastMode ? basePathSlash(window.location.pathname) : contrastModeUrl;
|
||||
const otherContrastModeTitle = contrastMode ? 'Switch to normal contrast' : 'Switch to high contrast';
|
||||
const forceRelayoutClassName = 'footer-label footer-label-icon';
|
||||
const forceRelayoutTitle = 'Force re-layout (might reduce edge crossings, but may shift nodes around)';
|
||||
const isPaused = updatePaused;
|
||||
const pauseClassName = isPaused ? 'footer-label footer-label-icon footer-label-active' : 'footer-label footer-label-icon';
|
||||
const updateCount = getUpdateBufferSize();
|
||||
const hasUpdates = updateCount > 0;
|
||||
const pauseTitle = isPaused ? `Paused on ${formatDate(updatePausedAt)}` : 'Pause updates';
|
||||
const pauseAction = isPaused ? clickResumeUpdate : clickPauseUpdate;
|
||||
const pauseWrapperClassName = isPaused ? 'footer-active' : '';
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
@@ -132,34 +117,7 @@ export default class App extends React.Component {
|
||||
activeOptions={this.state.activeTopologyOptions} />
|
||||
</Sidebar>
|
||||
|
||||
<div className="footer">
|
||||
<span className="footer-label">Version</span>
|
||||
{this.state.version}
|
||||
<span className="footer-label">on</span>
|
||||
{this.state.hostname}
|
||||
|
||||
|
||||
<span className={pauseWrapperClassName}>
|
||||
{!hasUpdates && isPaused && <span className="footer-label">Paused</span>}
|
||||
{hasUpdates && isPaused && <span className="footer-label">Paused ({updateCount} updates waiting)</span>}
|
||||
{hasUpdates && !isPaused && <span className="footer-label">Resuming ({updateCount} updates remaining)</span>}
|
||||
<a className={pauseClassName} onClick={pauseAction} title={pauseTitle}>
|
||||
<span className="fa fa-pause" />
|
||||
</a>
|
||||
</span>
|
||||
<a className={forceRelayoutClassName} onClick={clickForceRelayout} title={forceRelayoutTitle}>
|
||||
<span className="fa fa-refresh" />
|
||||
</a>
|
||||
<a className="footer-label footer-label-icon" onClick={clickDownloadGraph} title="Save canvas as SVG">
|
||||
<span className="fa fa-download" />
|
||||
</a>
|
||||
<a className="footer-label footer-label-icon" href={otherContrastModeUrl} title={otherContrastModeTitle}>
|
||||
<span className="fa fa-adjust" />
|
||||
</a>
|
||||
<a className="footer-label footer-label-icon" href="https://gitreports.com/issue/weaveworks/scope" target="_blank" title="Report an issue">
|
||||
<span className="fa fa-bug" />
|
||||
</a>
|
||||
</div>
|
||||
<Footer {...this.state} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
67
client/app/scripts/components/footer.js
Normal file
67
client/app/scripts/components/footer.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import { getUpdateBufferSize } from '../utils/update-buffer-utils';
|
||||
import { contrastModeUrl, isContrastMode } from '../utils/contrast-utils';
|
||||
import { clickDownloadGraph, clickForceRelayout, clickPauseUpdate,
|
||||
clickResumeUpdate } from '../actions/app-actions';
|
||||
import { basePathSlash } from '../utils/web-api-utils';
|
||||
|
||||
export default (props) => {
|
||||
const { hostname, updatePaused, updatePausedAt, version } = props;
|
||||
const contrastMode = isContrastMode();
|
||||
|
||||
// link url to switch contrast with current UI state
|
||||
const otherContrastModeUrl = contrastMode ? basePathSlash(window.location.pathname) : contrastModeUrl;
|
||||
const otherContrastModeTitle = contrastMode ? 'Switch to normal contrast' : 'Switch to high contrast';
|
||||
const forceRelayoutTitle = 'Force re-layout (might reduce edge crossings, but may shift nodes around)';
|
||||
|
||||
// pause button
|
||||
const isPaused = updatePaused;
|
||||
const updateCount = getUpdateBufferSize();
|
||||
const hasUpdates = updateCount > 0;
|
||||
const pausedAgo = moment(updatePausedAt).fromNow();
|
||||
const pauseTitle = isPaused ? `Paused ${pausedAgo}` : 'Pause updates (freezes the nodes in their current layout)';
|
||||
const pauseAction = isPaused ? clickResumeUpdate : clickPauseUpdate;
|
||||
const pauseClassName = isPaused ? 'footer-icon footer-icon-active' : 'footer-icon';
|
||||
let pauseLabel = '';
|
||||
if (hasUpdates && isPaused) {
|
||||
pauseLabel = `Paused +${updateCount}`;
|
||||
} else if (hasUpdates && !isPaused) {
|
||||
pauseLabel = `Resuming +${updateCount}`;
|
||||
} else if (!hasUpdates && isPaused) {
|
||||
pauseLabel = 'Paused';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="footer">
|
||||
|
||||
<div className="footer-status">
|
||||
<span className="footer-label">Version</span>
|
||||
{version}
|
||||
<span className="footer-label">on</span>
|
||||
{hostname}
|
||||
</div>
|
||||
|
||||
<div className="footer-tools">
|
||||
<a className={pauseClassName} onClick={pauseAction} title={pauseTitle}>
|
||||
{pauseLabel !== '' && <span className="footer-label">{pauseLabel}</span>}
|
||||
<span className="fa fa-pause" />
|
||||
</a>
|
||||
<a className="footer-icon" onClick={clickForceRelayout} title={forceRelayoutTitle}>
|
||||
<span className="fa fa-refresh" />
|
||||
</a>
|
||||
<a className="footer-icon" onClick={clickDownloadGraph} title="Save canvas as SVG">
|
||||
<span className="fa fa-download" />
|
||||
</a>
|
||||
<a className="footer-icon" href={otherContrastModeUrl} title={otherContrastModeTitle}>
|
||||
<span className="fa fa-adjust" />
|
||||
</a>
|
||||
<a className="footer-icon" href="https://gitreports.com/issue/weaveworks/scope" target="_blank" title="Report an issue">
|
||||
<span className="fa fa-bug" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -38,6 +38,58 @@ function maybeUpdate() {
|
||||
}
|
||||
}
|
||||
|
||||
// consolidate first buffer entry with second
|
||||
function consolidateBuffer() {
|
||||
const first = deltaBuffer.first();
|
||||
deltaBuffer = deltaBuffer.shift();
|
||||
const second = deltaBuffer.first();
|
||||
let toAdd = _.union(first.add, second.add);
|
||||
let toUpdate = _.union(first.update, second.update);
|
||||
let toRemove = _.union(first.remove, second.remove);
|
||||
log('Consolidating delta buffer', 'add', _.size(toAdd), 'update', _.size(toUpdate), 'remove', _.size(toRemove));
|
||||
|
||||
// check if an added node in first was updated in second -> add second update
|
||||
toAdd = _.map(toAdd, node => {
|
||||
const updateNode = _.find(second.update, {'id': node.id});
|
||||
if (updateNode) {
|
||||
toUpdate = _.reject(toUpdate, {'id': node.id});
|
||||
return updateNode;
|
||||
}
|
||||
return node;
|
||||
});
|
||||
|
||||
// check if an updated node in first was updated in second -> updated second update
|
||||
// no action needed, successive updates are fine
|
||||
|
||||
// check if an added node in first was removed in second -> dont add, dont remove
|
||||
_.each(first.add, node => {
|
||||
const removedNode = _.find(second.remove, {'id': node.id});
|
||||
if (removedNode) {
|
||||
toAdd = _.reject(toAdd, {'id': node.id});
|
||||
toRemove = _.reject(toRemove, {'id': node.id});
|
||||
}
|
||||
});
|
||||
|
||||
// check if an updated node in first was removed in second -> remove
|
||||
_.each(first.update, node => {
|
||||
const removedNode = _.find(second.remove, {'id': node.id});
|
||||
if (removedNode) {
|
||||
toUpdate = _.reject(toUpdate, {'id': node.id});
|
||||
}
|
||||
});
|
||||
|
||||
// check if an removed node in first was added in second -> update
|
||||
// remove -> add is fine for the store
|
||||
|
||||
// update buffer
|
||||
log('Consolidated delta buffer', 'add', _.size(toAdd), 'update', _.size(toUpdate), 'remove', _.size(toRemove));
|
||||
deltaBuffer.set(0, {
|
||||
add: toAdd.length > 0 ? toAdd : null,
|
||||
update: toUpdate.length > 0 ? toUpdate : null,
|
||||
remove: toRemove.length > 0 ? toRemove : null
|
||||
});
|
||||
}
|
||||
|
||||
export function bufferDeltaUpdate(delta) {
|
||||
if (delta.add === null && delta.update === null && delta.remove === null) {
|
||||
log('Discarding empty nodes delta');
|
||||
@@ -45,55 +97,7 @@ export function bufferDeltaUpdate(delta) {
|
||||
}
|
||||
|
||||
if (deltaBuffer.size >= bufferLength) {
|
||||
// consolidate first buffer entry with second
|
||||
const first = deltaBuffer.first();
|
||||
deltaBuffer = deltaBuffer.shift();
|
||||
const second = deltaBuffer.first();
|
||||
let toAdd = _.union(first.add, second.add);
|
||||
let toUpdate = _.union(first.update, second.update);
|
||||
let toRemove = _.union(first.remove, second.remove);
|
||||
log('Consolidating delta buffer', 'add', _.size(toAdd), 'update', _.size(toUpdate), 'remove', _.size(toRemove));
|
||||
|
||||
// check if an added node in first was updated in second -> add second update
|
||||
toAdd = _.map(toAdd, node => {
|
||||
const updateNode = _.find(second.update, {'id': node.id});
|
||||
if (updateNode) {
|
||||
toUpdate = _.reject(toUpdate, {'id': node.id});
|
||||
return updateNode;
|
||||
}
|
||||
return node;
|
||||
});
|
||||
|
||||
// check if an updated node in first was updated in second -> updated second update
|
||||
// no action needed, successive updates are fine
|
||||
|
||||
// check if an added node in first was removed in second -> dont add, dont remove
|
||||
_.each(first.add, node => {
|
||||
const removedNode = _.find(second.remove, {'id': node.id});
|
||||
if (removedNode) {
|
||||
toAdd = _.reject(toAdd, {'id': node.id});
|
||||
toRemove = _.reject(toRemove, {'id': node.id});
|
||||
}
|
||||
});
|
||||
|
||||
// check if an updated node in first was removed in second -> remove
|
||||
_.each(first.update, node => {
|
||||
const removedNode = _.find(second.remove, {'id': node.id});
|
||||
if (removedNode) {
|
||||
toUpdate = _.reject(toUpdate, {'id': node.id});
|
||||
}
|
||||
});
|
||||
|
||||
// check if an removed node in first was added in second -> update
|
||||
// remove -> add is fine for the store
|
||||
|
||||
// update buffer
|
||||
log('Consolidated delta buffer', 'add', _.size(toAdd), 'update', _.size(toUpdate), 'remove', _.size(toRemove));
|
||||
deltaBuffer.set(0, {
|
||||
add: toAdd.length > 0 ? toAdd : null,
|
||||
update: toUpdate.length > 0 ? toUpdate : null,
|
||||
remove: toRemove.length > 0 ? toRemove : null
|
||||
});
|
||||
consolidateBuffer();
|
||||
}
|
||||
|
||||
deltaBuffer = deltaBuffer.push(delta);
|
||||
|
||||
@@ -179,6 +179,7 @@ h2 {
|
||||
z-index: 20;
|
||||
color: @text-tertiary-color;
|
||||
font-size: 0.7rem;
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
color: @text-secondary-color;
|
||||
@@ -186,33 +187,45 @@ h2 {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 90%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-active {
|
||||
animation: blinking 1.5s infinite ease-in-out;
|
||||
&-status {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
&-label {
|
||||
text-transform: uppercase;
|
||||
margin: 0 0.25em;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
margin-left: 0.5em;
|
||||
padding: 4px 3px;
|
||||
color: @text-color;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10%;
|
||||
&:hover {
|
||||
border: 1px solid @text-tertiary-color;
|
||||
}
|
||||
span {
|
||||
font-size: 150%;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
&-icon {
|
||||
margin-left: 0.5em;
|
||||
padding: 4px 3px;
|
||||
color: @text-color;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid @text-tertiary-color;
|
||||
}
|
||||
|
||||
.fa {
|
||||
font-size: 150%;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
&-active {
|
||||
border: 1px solid @text-tertiary-color;
|
||||
animation: blinking 1.5s infinite ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon &-label {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"immutable": "~3.7.4",
|
||||
"lodash": "~3.10.1",
|
||||
"materialize-css": "0.97.2",
|
||||
"moment": "2.12.0",
|
||||
"page": "1.6.4",
|
||||
"react": "0.14.3",
|
||||
"react-addons-pure-render-mixin": "0.14.3",
|
||||
|
||||
Reference in New Issue
Block a user