mirror of
https://github.com/xodio/xod.git
synced 2026-02-20 02:01:20 +01:00
feat(xod-client): add table log tab, show data, switch sources and sheets, copy, save
This commit is contained in:
@@ -52,4 +52,10 @@
|
||||
box-sizing: border-box;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
&--inline {
|
||||
height: 22px;
|
||||
line-height: 0;
|
||||
padding: 0.2rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ span.data-grid-container, span.data-grid-container:focus {
|
||||
}
|
||||
|
||||
.data-grid-container .data-grid .cell.read-only {
|
||||
background: whitesmoke;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -92,7 +91,7 @@ span.data-grid-container, span.data-grid-container:focus {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.data-grid-container tr:first-child .cell {
|
||||
.data-grid.with-header tr:first-child .cell {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
background-color: $grey;
|
||||
|
||||
93
packages/xod-client/src/core/styles/components/TableLog.scss
Normal file
93
packages/xod-client/src/core/styles/components/TableLog.scss
Normal file
@@ -0,0 +1,93 @@
|
||||
@mixin header-vertical-centered {
|
||||
height: 22px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.TableLog {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
flex: 1;
|
||||
flex-grow: 1;
|
||||
flex-flow: column;
|
||||
|
||||
&-header {
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
padding: 1px 3px 1px 8px;
|
||||
|
||||
.sourceSelector {
|
||||
@include header-vertical-centered;
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
|
||||
label {
|
||||
color: $chalk;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 300px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
.sheets {
|
||||
@include header-vertical-centered;
|
||||
float: left;
|
||||
margin-left: 1rem;
|
||||
|
||||
.currentSheet {
|
||||
display: inline-block;
|
||||
margin: 0 .5rem;
|
||||
color: $chalk;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
.actions {
|
||||
@include header-vertical-centered;
|
||||
float: right;
|
||||
margin-left: 1rem;
|
||||
|
||||
button {
|
||||
margin-left: 2px;
|
||||
|
||||
.fa {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: scroll;
|
||||
|
||||
background: $grey;
|
||||
|
||||
.no-data {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -.5em;
|
||||
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
color: $chalk;
|
||||
font-size: $font-size-l;
|
||||
}
|
||||
|
||||
.data-grid {
|
||||
margin-top: -2px; // compensate border-spacing
|
||||
}
|
||||
|
||||
@include styled-scrollbar();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@
|
||||
'components/SnappingPreviewLayer',
|
||||
'components/SplitPane',
|
||||
'components/Suggester',
|
||||
'components/TableLog',
|
||||
'components/Tabs',
|
||||
'components/TabsContainer',
|
||||
'components/TabsItem',
|
||||
|
||||
@@ -36,7 +36,7 @@ export const startDebuggerSession = (
|
||||
nodePinKeysMap,
|
||||
tableLogNodeIds,
|
||||
pinsAffectedByErrorRaisers,
|
||||
currentPatchPath,
|
||||
patchPath,
|
||||
globals,
|
||||
tetheringInetNodeId
|
||||
) => ({
|
||||
@@ -47,7 +47,7 @@ export const startDebuggerSession = (
|
||||
nodePinKeysMap,
|
||||
tableLogNodeIds,
|
||||
pinsAffectedByErrorRaisers,
|
||||
patchPath: currentPatchPath,
|
||||
patchPath,
|
||||
globals,
|
||||
tetheringInetNodeId,
|
||||
},
|
||||
|
||||
@@ -24,3 +24,5 @@ export const SESSION_TYPE = {
|
||||
SERIAL: 'serial',
|
||||
SIMULATON: 'simulation',
|
||||
};
|
||||
|
||||
export const NEW_SHEET = String.fromCharCode(0xc);
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
|
||||
import * as EAT from '../editor/actionTypes';
|
||||
|
||||
import { UPLOAD_MSG_TYPE, LOG_TAB_TYPE, SESSION_TYPE } from './constants';
|
||||
import { UPLOAD_MSG_TYPE, LOG_TAB_TYPE, SESSION_TYPE, NEW_SHEET } from './constants';
|
||||
import * as MSG from './messages';
|
||||
import { STATUS } from '../utils/constants';
|
||||
import { isXodErrorMessage } from './debugProtocol';
|
||||
@@ -157,7 +157,6 @@ const updateInteractiveNodeValues = R.curry((messageList, state) => {
|
||||
)(watchNodeMessages);
|
||||
|
||||
// Prepare data for table logs
|
||||
const NEW_SHEET = String.fromCharCode(0xc); // TODO: Move somewhere
|
||||
const newTableLogData = R.map(
|
||||
R.compose(
|
||||
R.reduce(
|
||||
|
||||
@@ -296,3 +296,20 @@ export const tetheringInetTransmitter = R.compose(
|
||||
R.path(['tetheringInet', 'transmitter']),
|
||||
getDebuggerState
|
||||
);
|
||||
|
||||
// =============================================================================
|
||||
//
|
||||
// Table log
|
||||
//
|
||||
// =============================================================================
|
||||
|
||||
export const getTableLogValues = R.compose(
|
||||
R.prop('tableLogValues'),
|
||||
getDebuggerState
|
||||
);
|
||||
|
||||
export const getTableLogSources = R.compose(R.keys, getTableLogValues);
|
||||
|
||||
export const getTableLogsByNodeId = R.curry((nodeId, state) =>
|
||||
R.compose(R.pathOr([], ['tableLogValues', nodeId]), getDebuggerState)(state)
|
||||
);
|
||||
|
||||
@@ -69,3 +69,6 @@ export const NODE_PROPERTY_UPDATING = 'NODE_PROPERTY_UPDATING';
|
||||
|
||||
export const SHOW_COLORPICKER_WIDGET = 'SHOW_COLORPICKER_WIDGET';
|
||||
export const HIDE_COLORPICKER_WIDGET = 'HIDE_COLORPICKER_WIDGET';
|
||||
|
||||
export const OPEN_TABLE_LOG_TAB = 'OPEN_TABLE_LOG_TAB';
|
||||
export const CHANGE_TABLE_LOG_SHEET = 'CHANGE_TABLE_LOG_SHEET';
|
||||
|
||||
@@ -940,3 +940,39 @@ export const tweakNodeProperty = (nodeId, kind, key, value) => (
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const openTableLogTab = nodeId => (dispatch, getState) => {
|
||||
const sheets = DebuggerSelectors.getTableLogsByNodeId(nodeId, getState());
|
||||
return dispatch({
|
||||
type: ActionType.OPEN_TABLE_LOG_TAB,
|
||||
payload: {
|
||||
nodeId,
|
||||
activeSheetIndex: sheets.length > 0 ? sheets.length - 1 : 0,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const changeActiveSheet = R.curry((tabId, nodeId, newSheetIndex) => ({
|
||||
type: ActionType.CHANGE_TABLE_LOG_SHEET,
|
||||
payload: {
|
||||
tabId,
|
||||
nodeId,
|
||||
newSheetIndex,
|
||||
},
|
||||
}));
|
||||
|
||||
export const changeTableLogSource = (tabId, nodeId) => (dispatch, getState) => {
|
||||
const newSourceSheets = DebuggerSelectors.getTableLogsByNodeId(
|
||||
nodeId,
|
||||
getState()
|
||||
);
|
||||
const newSheetIndex = R.max(0, newSourceSheets.length - 1);
|
||||
dispatch({
|
||||
type: ActionType.CHANGE_TABLE_LOG_SHEET,
|
||||
payload: {
|
||||
tabId,
|
||||
nodeId,
|
||||
newSheetIndex,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -99,6 +99,7 @@ const TabtestEditor = ({
|
||||
</div>
|
||||
<div className="tabtest-editor">
|
||||
<ReactDataSheet
|
||||
className="with-header"
|
||||
data={cells}
|
||||
valueRenderer={R.prop('value')}
|
||||
overflow={'nowrap'}
|
||||
|
||||
@@ -64,9 +64,11 @@ export const CLIPBOARD_DATA_TYPE = 'text/xod-entities';
|
||||
export const TAB_TYPES = {
|
||||
PATCH: 'PATCH',
|
||||
DEBUGGER: 'DEBUGGER',
|
||||
TABLE_LOG: 'TABLE_LOG',
|
||||
};
|
||||
|
||||
export const DEBUGGER_TAB_ID = 'debugger';
|
||||
export const TABLE_LOG_TAB_ID = 'tablelog';
|
||||
|
||||
export const PANEL_IDS = {
|
||||
PROJECT_BROWSER: 'PROJECT_BROWSER',
|
||||
|
||||
@@ -39,6 +39,7 @@ import Workarea from './Workarea';
|
||||
|
||||
import Tabs from './Tabs';
|
||||
import DragLayer from './DragLayer';
|
||||
import TableLog from './TableLog';
|
||||
|
||||
const pickPropsToCheck = R.compose(
|
||||
R.evolve({ panelSettings: R.map(R.omit(['size'])) }),
|
||||
@@ -111,6 +112,11 @@ class Editor extends React.Component {
|
||||
return foldMaybe(
|
||||
<NoPatch />,
|
||||
tab => {
|
||||
// Render <Patch /> component only for PATCH or DEBUGGER
|
||||
// tab types
|
||||
if (tab.type !== TAB_TYPES.PATCH && tab.type !== TAB_TYPES.DEBUGGER)
|
||||
return null;
|
||||
|
||||
// Do not render <Patch /> component if opened tab
|
||||
// is in EditingCppImplementation mode.
|
||||
if (tab.editedAttachment) return null;
|
||||
@@ -139,12 +145,37 @@ class Editor extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderTableLogTab() {
|
||||
const { currentTab } = this.props;
|
||||
|
||||
return foldMaybe(
|
||||
null,
|
||||
tab => {
|
||||
// Render only for TABLE_LOG tab type
|
||||
if (tab.type !== TAB_TYPES.TABLE_LOG) return null;
|
||||
|
||||
return (
|
||||
<TableLog
|
||||
tabId={tab.id}
|
||||
nodeId={tab.nodeId}
|
||||
sheetIndex={tab.activeSheetIndex}
|
||||
/>
|
||||
);
|
||||
},
|
||||
currentTab
|
||||
);
|
||||
}
|
||||
|
||||
renderOpenedAttachmentEditorTabs() {
|
||||
return foldMaybe(
|
||||
null,
|
||||
currentTab => {
|
||||
const tabs = this.props.attachmentEditorTabs.map(
|
||||
({ id, type, patchPath, editedAttachment }) => {
|
||||
// Render only for PATCH or DEBUGGER tab types
|
||||
if (type !== TAB_TYPES.PATCH && type !== TAB_TYPES.DEBUGGER)
|
||||
return null;
|
||||
|
||||
const patch = XP.getPatchByPathUnsafe(
|
||||
patchPath,
|
||||
this.props.project
|
||||
@@ -156,11 +187,6 @@ class Editor extends React.Component {
|
||||
R.propOr('', editedAttachment, XP.MANAGED_ATTACHMENT_TEMPLATES)
|
||||
);
|
||||
|
||||
const currentPatchPath = explodeMaybe(
|
||||
'No currentPatchPath, but currentTab exists',
|
||||
this.props.currentPatchPath
|
||||
);
|
||||
|
||||
switch (editedAttachment) {
|
||||
case XP.TABTEST_MARKER_PATH:
|
||||
return (
|
||||
@@ -177,11 +203,9 @@ class Editor extends React.Component {
|
||||
}
|
||||
isInDebuggerTab={type === TAB_TYPES.DEBUGGER}
|
||||
isRunning={this.props.isTabtestRunning}
|
||||
onRunClick={() =>
|
||||
this.props.actions.runTabtest(currentPatchPath)
|
||||
}
|
||||
onRunClick={() => this.props.actions.runTabtest(patchPath)}
|
||||
onClose={this.props.actions.closeAttachmentEditor}
|
||||
patchPath={currentPatchPath}
|
||||
patchPath={patchPath}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -200,7 +224,7 @@ class Editor extends React.Component {
|
||||
}
|
||||
isInDebuggerTab={type === TAB_TYPES.DEBUGGER}
|
||||
onClose={this.props.actions.closeAttachmentEditor}
|
||||
patchPath={currentPatchPath}
|
||||
patchPath={patchPath}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -282,6 +306,7 @@ class Editor extends React.Component {
|
||||
>
|
||||
{this.renderOpenedPatchTab()}
|
||||
{this.renderOpenedAttachmentEditorTabs()}
|
||||
{this.renderTableLogTab()}
|
||||
<SnackBar />
|
||||
</Workarea>
|
||||
</FocusTrap>
|
||||
|
||||
@@ -382,6 +382,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
addInteractiveNode: ProjectActions.addInteractiveNode,
|
||||
focusBoundValue: EditorActions.focusBoundValue,
|
||||
focusLabel: EditorActions.focusLabel,
|
||||
openTableLogTab: EditorActions.openTableLogTab,
|
||||
},
|
||||
dispatch
|
||||
),
|
||||
|
||||
@@ -16,6 +16,8 @@ const debuggingMode = R.merge(selectingMode, {
|
||||
onNodeDoubleClick(api, nodeId, patchPath) {
|
||||
if (R.contains(patchPath, R.keys(XP.MANAGED_ATTACHMENT_FILENAMES))) {
|
||||
api.props.actions.openAttachmentEditor(patchPath);
|
||||
} else if (XP.isTableLogPatchPath(patchPath)) {
|
||||
api.props.actions.openTableLogTab(nodeId);
|
||||
} else if (
|
||||
XP.isConstantNodeType(patchPath) ||
|
||||
XP.isTweakPath(patchPath) ||
|
||||
|
||||
@@ -231,6 +231,8 @@ const selectingMode = {
|
||||
onNodeDoubleClick(api, nodeId, patchPath) {
|
||||
if (R.contains(patchPath, R.keys(XP.MANAGED_ATTACHMENT_FILENAMES))) {
|
||||
api.props.actions.openAttachmentEditor(patchPath);
|
||||
} else if (XP.isTableLogPatchPath(patchPath)) {
|
||||
api.props.actions.openTableLogTab(nodeId);
|
||||
} else if (
|
||||
XP.isConstantNodeType(patchPath) ||
|
||||
XP.isTweakPath(patchPath) ||
|
||||
|
||||
229
packages/xod-client/src/editor/containers/TableLog.jsx
Normal file
229
packages/xod-client/src/editor/containers/TableLog.jsx
Normal file
@@ -0,0 +1,229 @@
|
||||
import * as R from 'ramda';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import ReactDataSheet from 'react-datasheet';
|
||||
import { Icon } from 'react-fa';
|
||||
|
||||
import * as Actions from '../actions';
|
||||
import * as DebuggerSelectors from '../../debugger/selectors';
|
||||
|
||||
import { addConfirmation } from '../../messages/actions';
|
||||
import {
|
||||
LOG_COPIED,
|
||||
LOG_COPY_NOT_SUPPORTED,
|
||||
logCopyError,
|
||||
logSaveError,
|
||||
} from '../../debugger/messages';
|
||||
|
||||
// :: [[String]] -> [[{ value, readOnly }]]
|
||||
const tableLogToDataSheet = R.map(
|
||||
R.map(
|
||||
R.applySpec({
|
||||
value: R.identity,
|
||||
readOnly: R.T,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const gridToTsv = R.compose(R.join('\n'), R.map(R.join('\t')));
|
||||
|
||||
class TableLog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.getSheet = this.getSheet.bind(this);
|
||||
|
||||
this.onPrevSheetClick = this.onPrevSheetClick.bind(this);
|
||||
this.onNextSheetClick = this.onNextSheetClick.bind(this);
|
||||
this.onCopyLogClicked = this.onCopyLogClicked.bind(this);
|
||||
this.onSaveLogClicked = this.onSaveLogClicked.bind(this);
|
||||
this.onChangeSource = this.onChangeSource.bind(this);
|
||||
}
|
||||
|
||||
onPrevSheetClick() {
|
||||
const prevSheetIndex = R.max(0, this.props.sheetIndex - 1);
|
||||
this.props.actions.changeActiveSheet(
|
||||
this.props.tabId, // TODO: what's about debugger?
|
||||
this.props.nodeId,
|
||||
prevSheetIndex
|
||||
);
|
||||
}
|
||||
|
||||
onNextSheetClick() {
|
||||
const nextSheetIndex = R.unless(R.equals(this.getSheetsAmount()), R.add(1))(
|
||||
this.props.sheetIndex
|
||||
);
|
||||
this.props.actions.changeActiveSheet(
|
||||
this.props.tabId,
|
||||
this.props.nodeId,
|
||||
nextSheetIndex
|
||||
);
|
||||
}
|
||||
|
||||
onCopyLogClicked() {
|
||||
const copy = R.path(['navigator', 'clipboard', 'writeText'], window);
|
||||
if (!copy) {
|
||||
this.props.actions.addError(LOG_COPY_NOT_SUPPORTED);
|
||||
return;
|
||||
}
|
||||
|
||||
const tsv = gridToTsv(this.getSheet());
|
||||
window.navigator.clipboard.writeText(tsv).then(
|
||||
() => {
|
||||
this.props.actions.addConfirmation(LOG_COPIED);
|
||||
},
|
||||
err => {
|
||||
this.props.actions.addError(logCopyError(err));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onSaveLogClicked() {
|
||||
const tsv = gridToTsv(this.getSheet());
|
||||
const defaultFilename = `log-${this.props.nodeId}-${
|
||||
this.props.sheetIndex
|
||||
}.txt`;
|
||||
|
||||
try {
|
||||
const data = new window.Blob([tsv], { type: 'text/plain' });
|
||||
const file = window.URL.createObjectURL(data);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.download = defaultFilename;
|
||||
link.href = file;
|
||||
link.click();
|
||||
|
||||
// We need to manually revoke the object URL to avoid memory leaks.
|
||||
window.URL.revokeObjectURL(file);
|
||||
} catch (err) {
|
||||
this.props.actions.addError(logSaveError(err));
|
||||
}
|
||||
}
|
||||
|
||||
onChangeSource(event) {
|
||||
const sourceNodeId = event.target.value;
|
||||
this.props.actions.changeSource(this.props.tabId, sourceNodeId);
|
||||
}
|
||||
|
||||
getSheet() {
|
||||
return R.propOr([], this.props.sheetIndex, this.props.document);
|
||||
}
|
||||
|
||||
getSheetsAmount() {
|
||||
return this.props.document.length;
|
||||
}
|
||||
|
||||
isEmptySheet() {
|
||||
return this.getSheet().length === 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasNoSources = this.props.sources.length === 0;
|
||||
const sheetAmount =
|
||||
this.getSheetsAmount() === 0 ? 1 : this.getSheetsAmount();
|
||||
|
||||
return (
|
||||
<div className="TableLog">
|
||||
<div className="TableLog-header">
|
||||
<div className="sourceSelector">
|
||||
<select
|
||||
id="tablelog-source"
|
||||
value={this.props.nodeId}
|
||||
onChange={this.onChangeSource}
|
||||
disabled={hasNoSources}
|
||||
>
|
||||
{hasNoSources ? <option>No logs</option> : null}
|
||||
{this.props.sources.map(nodeId => (
|
||||
<option key={nodeId} value={nodeId}>
|
||||
{nodeId}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="sheets">
|
||||
<button
|
||||
className="prev Button Button--light Button--inline"
|
||||
onClick={this.onPrevSheetClick}
|
||||
disabled={this.props.sheetIndex === 0}
|
||||
>
|
||||
<Icon name="angle-left" />
|
||||
</button>
|
||||
<div className="currentSheet">
|
||||
{this.props.sheetIndex + 1} / {sheetAmount}
|
||||
</div>
|
||||
<button
|
||||
className="next Button Button--light Button--inline"
|
||||
onClick={this.onNextSheetClick}
|
||||
disabled={this.props.sheetIndex >= this.getSheetsAmount() - 1}
|
||||
>
|
||||
<Icon name="angle-right" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button
|
||||
className="copy-log Button Button--light Button--inline"
|
||||
onClick={this.onCopyLogClicked}
|
||||
disabled={this.isEmptySheet()}
|
||||
>
|
||||
<Icon name="copy" title="Download Log" />
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
className="save-log Button Button--light Button--inline"
|
||||
onClick={this.onSaveLogClicked}
|
||||
disabled={this.isEmptySheet()}
|
||||
>
|
||||
<Icon name="save" title="Download Log" />
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="TableLog-content">
|
||||
{this.isEmptySheet() ? (
|
||||
<span className="no-data">No data stored</span>
|
||||
) : (
|
||||
<ReactDataSheet
|
||||
className="tablelog-grid"
|
||||
data={tableLogToDataSheet(this.getSheet())}
|
||||
valueRenderer={cell => cell.value}
|
||||
overflow={'nowrap'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableLog.propTypes = {
|
||||
// from parent
|
||||
tabId: PropTypes.string.isRequired,
|
||||
// connect
|
||||
sources: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
document: PropTypes.array.isRequired,
|
||||
nodeId: PropTypes.string.isRequired,
|
||||
sheetIndex: PropTypes.number.isRequired,
|
||||
actions: PropTypes.objectOf(PropTypes.func),
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, { nodeId }) =>
|
||||
R.applySpec({
|
||||
sources: DebuggerSelectors.getTableLogSources,
|
||||
document: DebuggerSelectors.getTableLogsByNodeId(nodeId),
|
||||
})(state);
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
actions: bindActionCreators(
|
||||
{
|
||||
changeActiveSheet: Actions.changeActiveSheet,
|
||||
changeSource: Actions.changeTableLogSource,
|
||||
addConfirmation,
|
||||
},
|
||||
dispatch
|
||||
),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TableLog);
|
||||
@@ -11,6 +11,7 @@ import { DEFAULT_PANNING_OFFSET } from '../project/nodeLayout';
|
||||
import { MAIN_PATCH_PATH } from '../project/constants';
|
||||
import {
|
||||
DEBUGGER_TAB_ID,
|
||||
TABLE_LOG_TAB_ID,
|
||||
FOCUS_AREAS,
|
||||
SELECTION_ENTITY_TYPE,
|
||||
TAB_TYPES,
|
||||
@@ -130,34 +131,52 @@ const setPropsToTab = R.curry((id, props, state) =>
|
||||
)(state)
|
||||
);
|
||||
|
||||
const addTabWithProps = R.curry((id, type, patchPath, state) => {
|
||||
const getTabNewIndex = state => {
|
||||
const tabs = R.prop('tabs')(state);
|
||||
const lastIndex = R.reduce(
|
||||
(acc, tab) => R.pipe(R.prop('index'), R.max(acc))(tab),
|
||||
-1,
|
||||
R.values(tabs)
|
||||
);
|
||||
const newIndex = R.inc(lastIndex);
|
||||
return R.inc(lastIndex);
|
||||
};
|
||||
|
||||
return setPropsToTab(
|
||||
const addTabWithProps = R.curry((id, type, patchPath, state) =>
|
||||
setPropsToTab(
|
||||
id,
|
||||
{
|
||||
id,
|
||||
patchPath,
|
||||
index: newIndex,
|
||||
index: getTabNewIndex(state),
|
||||
type,
|
||||
offset: DEFAULT_PANNING_OFFSET,
|
||||
editedAttachment: null,
|
||||
},
|
||||
state
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
|
||||
const addPatchTab = R.curry((newId, patchPath, state) => {
|
||||
if (!patchPath) return state;
|
||||
return addTabWithProps(newId, TAB_TYPES.PATCH, patchPath, state);
|
||||
});
|
||||
|
||||
const openTableLogTab = R.curry((nodeId, activeSheetIndex, state) =>
|
||||
setPropsToTab(
|
||||
TABLE_LOG_TAB_ID,
|
||||
R.unless(
|
||||
() => isTabOpened(TABLE_LOG_TAB_ID, state),
|
||||
R.merge(R.__, { index: getTabNewIndex(state) })
|
||||
)({
|
||||
id: TABLE_LOG_TAB_ID,
|
||||
type: TAB_TYPES.TABLE_LOG,
|
||||
nodeId,
|
||||
activeSheetIndex,
|
||||
}),
|
||||
state
|
||||
)
|
||||
);
|
||||
|
||||
const applyTabSort = (tab, payload) => {
|
||||
if (R.not(R.has(tab.id, payload))) {
|
||||
return tab;
|
||||
@@ -711,6 +730,20 @@ const editorReducer = (state = initialState, action) => {
|
||||
R.lensProp('pointingPopups'),
|
||||
R.assoc('colorPickerWidget', null)
|
||||
)(state);
|
||||
case EAT.OPEN_TABLE_LOG_TAB:
|
||||
return R.compose(
|
||||
R.assoc('currentTabId', TABLE_LOG_TAB_ID),
|
||||
openTableLogTab(action.payload.nodeId, action.payload.activeSheetIndex)
|
||||
)(state);
|
||||
case EAT.CHANGE_TABLE_LOG_SHEET:
|
||||
return setPropsToTab(
|
||||
action.payload.tabId,
|
||||
{
|
||||
nodeId: action.payload.nodeId,
|
||||
activeSheetIndex: action.payload.newSheetIndex,
|
||||
},
|
||||
state
|
||||
);
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as R from 'ramda';
|
||||
import { Maybe } from 'ramda-fantasy';
|
||||
import { createSelector } from 'reselect';
|
||||
import { mapIndexed, foldMaybe } from 'xod-func-tools';
|
||||
import { mapIndexed, foldMaybe, maybeProp } from 'xod-func-tools';
|
||||
import * as XP from 'xod-project';
|
||||
|
||||
import {
|
||||
@@ -32,37 +32,49 @@ export const getCurrentTabId = R.pipe(getEditor, R.prop('currentTabId'), Maybe);
|
||||
|
||||
export const getCurrentTab = createSelector(
|
||||
[getCurrentTabId, getTabs],
|
||||
(maybeTabId, tabs) => maybeTabId.chain(R.compose(Maybe, R.prop(R.__, tabs)))
|
||||
(maybeTabId, tabs) => R.chain(maybeProp(R.__, tabs))(maybeTabId)
|
||||
);
|
||||
|
||||
export const getCurrentPatchPath = createSelector(
|
||||
getCurrentTab,
|
||||
R.map(R.prop('patchPath'))
|
||||
R.chain(maybeProp('patchPath'))
|
||||
);
|
||||
|
||||
export const getCurrentPatchOffset = createSelector(
|
||||
getCurrentTab,
|
||||
foldMaybe(DEFAULT_PANNING_OFFSET, R.prop('offset'))
|
||||
foldMaybe(DEFAULT_PANNING_OFFSET, R.propOr(DEFAULT_PANNING_OFFSET, 'offset'))
|
||||
);
|
||||
|
||||
const isTabTypeEq = R.curry((expected, tab) => tab.type === expected);
|
||||
|
||||
const setTabLabel = R.assoc('label');
|
||||
|
||||
// :: State -> EditorTabs
|
||||
export const getPreparedTabs = createSelector(
|
||||
[getCurrentTabId, getTabs],
|
||||
(maybeCurrentTabId, tabs) => {
|
||||
const currentTabId = foldMaybe(null, R.identity, maybeCurrentTabId);
|
||||
return R.map(tab => {
|
||||
const patchPath = tab.patchPath;
|
||||
|
||||
const label =
|
||||
tab.type === TAB_TYPES.DEBUGGER
|
||||
? 'Debugger'
|
||||
: XP.getBaseName(patchPath);
|
||||
|
||||
return R.merge(tab, {
|
||||
label,
|
||||
isActive: currentTabId === tab.id,
|
||||
});
|
||||
}, tabs);
|
||||
return R.map(
|
||||
R.compose(
|
||||
tab => R.assoc('isActive', currentTabId === tab.id, tab),
|
||||
R.cond([
|
||||
[isTabTypeEq(TAB_TYPES.TABLE_LOG), setTabLabel('Table Logs')],
|
||||
[isTabTypeEq(TAB_TYPES.DEBUGGER), setTabLabel('Debugger')],
|
||||
[
|
||||
isTabTypeEq(TAB_TYPES.PATCH),
|
||||
tab =>
|
||||
R.compose(
|
||||
setTabLabel(R.__, tab),
|
||||
XP.getBaseName,
|
||||
R.prop('patchPath')
|
||||
)(tab),
|
||||
],
|
||||
// It should not be possible:
|
||||
[R.T, setTabLabel('Unknown Tab Type')],
|
||||
])
|
||||
),
|
||||
tabs
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user