depGraphEditor, ObjectInfoDirectory, v42, jadeToPug
This commit is contained in:
Родитель
fabfa9b4b4
Коммит
09ce9a3932
13
Dockerfile
13
Dockerfile
|
@ -1,3 +1,12 @@
|
|||
FROM heroku/nodejs
|
||||
FROM node:8.9.3
|
||||
|
||||
RUN npm run build
|
||||
# Copy package.json and package-lock.json then install deps
|
||||
WORKDIR /usr/src/app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
CMD ["npm", "start"]
|
|
@ -47,10 +47,10 @@ All of the instructions below assume that you are within the root of the project
|
|||
```sh
|
||||
heroku login
|
||||
```
|
||||
1. The Docker add-on must be installed
|
||||
1. Log into the Heroku Container Registry
|
||||
|
||||
```sh
|
||||
heroku plugins:install heroku-docker
|
||||
heroku container:login
|
||||
```
|
||||
1. The Heroku application must be created
|
||||
|
||||
|
@ -60,7 +60,7 @@ All of the instructions below assume that you are within the root of the project
|
|||
1. Deploy the application
|
||||
|
||||
```sh
|
||||
heroku docker:release
|
||||
heroku container:push web
|
||||
```
|
||||
|
||||
### (Optional) Set the Default Login URL and Consumer Key
|
||||
|
|
4
app.js
4
app.js
|
@ -88,9 +88,9 @@ function httpError(exception, socket) {
|
|||
|
||||
var app = express();
|
||||
|
||||
// use Jade view templates
|
||||
// use Pug view templates
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'jade');
|
||||
app.set('view engine', 'pug');
|
||||
|
||||
// make JSON bodies and cookies available in req objects
|
||||
app.use(bodyParser.urlencoded({
|
||||
|
|
|
@ -33,7 +33,7 @@ module.exports = {
|
|||
defaultConsumerKey: process.env.CONSUMER_KEY,
|
||||
|
||||
// API version
|
||||
currentVersion: 41,
|
||||
currentVersion: 42,
|
||||
|
||||
proxySettings: proxySettings
|
||||
};
|
||||
|
|
|
@ -130,9 +130,12 @@ export const finishedRecordDelete = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const editRecord = () => {
|
||||
export const editRecord = (creds, apiName, recordType) => {
|
||||
return {
|
||||
type: 'EDIT_RECORD'
|
||||
type: 'EDIT_RECORD',
|
||||
creds,
|
||||
apiName,
|
||||
recordType
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,6 +147,25 @@ export const updateFieldValue = (field, value) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const updateDepGraphFieldValue = (field, value, editValues, picklists, modalFields, fieldTree) => {
|
||||
return {
|
||||
type: 'UPDATE_DEP_GRAPH_FIELD_VALUE',
|
||||
field,
|
||||
value,
|
||||
picklists,
|
||||
modalFields,
|
||||
editValues,
|
||||
fieldTree
|
||||
}
|
||||
}
|
||||
|
||||
export const updatePicklistFields = (picklistFields) => {
|
||||
return {
|
||||
type: 'UPDATE_PICKLIST_FIELDS',
|
||||
picklistFields
|
||||
}
|
||||
}
|
||||
|
||||
export const recordUpdateSuccess = (recordData) => {
|
||||
return {
|
||||
type: 'RECORD_UPDATE_SUCCESS',
|
||||
|
@ -151,22 +173,41 @@ export const recordUpdateSuccess = (recordData) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const fetchPicklist = (creds, url) => {
|
||||
export const fetchPicklists = (creds, apiName, recordType) => {
|
||||
return {
|
||||
type: 'FETCH_PICKLIST',
|
||||
type: 'FETCH_PICKLISTS',
|
||||
creds,
|
||||
url
|
||||
apiName,
|
||||
recordType
|
||||
}
|
||||
}
|
||||
|
||||
export const receivePicklist = (url, result) => {
|
||||
export const receivePicklists = (url, result) => {
|
||||
return {
|
||||
type: 'RECEIVE_PICKLIST',
|
||||
type: 'RECEIVE_PICKLISTS',
|
||||
url,
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
export const editDepGraph = (picklists, modalFields, editValues, fieldTree, prevMode) => {
|
||||
return {
|
||||
type: 'EDIT_DEP_GRAPH',
|
||||
picklists,
|
||||
modalFields,
|
||||
editValues,
|
||||
fieldTree,
|
||||
prevMode
|
||||
}
|
||||
}
|
||||
|
||||
export const closeDepGraph = (mode) => {
|
||||
return {
|
||||
type: 'CLOSE_DEP_GRAPH',
|
||||
mode
|
||||
}
|
||||
}
|
||||
|
||||
export const showError = (response, responseJson) => {
|
||||
return {
|
||||
type: 'SHOW_ERROR',
|
||||
|
|
|
@ -14,10 +14,10 @@ const CreateableEntitiesList = ({creds, onChange, entities}) => {
|
|||
onChange={(event) => {if (event.target.value != "") {onChange(creds, event.target.value)}}}>
|
||||
[
|
||||
<option key="" value="">Select an object...</option>
|
||||
{ entities.sobjects.filter(x => x.createable).map((sobject) => {
|
||||
{ Object.keys(entities.sobjects).map(function (key) {
|
||||
return <option
|
||||
key={sobject.name}
|
||||
value={sobject.name}>{sobject.label}</option>
|
||||
key={entities.sobjects[key].apiName}
|
||||
value={entities.sobjects[key].apiName}>{entities.sobjects[key].label}</option>
|
||||
})}
|
||||
]
|
||||
</select>
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
import React, { PropTypes } from 'react'
|
||||
import RecordButton from './RecordButton'
|
||||
import {ModalContainer, ModalDialog} from 'react-modal-dialog'
|
||||
|
||||
function createNodeMap(fieldTree, nodeMap = [], size = 0 ) {
|
||||
for (var property in fieldTree) {
|
||||
nodeMap.push({property, size});
|
||||
createNodeMap(fieldTree[property], nodeMap, size + 1);
|
||||
}
|
||||
return nodeMap;
|
||||
}
|
||||
|
||||
function getEditorNodes(fieldTree, fieldComponents, fieldLabels){
|
||||
const nodeArray = createNodeMap(fieldTree);
|
||||
const nodeElArray = [];
|
||||
nodeArray.forEach((value, key, nodeArray) => {
|
||||
var divStyle = {
|
||||
paddingLeft: ''+ 100 * value.size + 'px'
|
||||
};
|
||||
|
||||
var nodeEl = <div key={"depGraphEdit" + value.property} style={divStyle}>{fieldLabels[value.property]} : {fieldComponents[value.property]}</div>
|
||||
nodeElArray.push(nodeEl);
|
||||
});
|
||||
return nodeElArray;
|
||||
}
|
||||
|
||||
// Dependency graph editor
|
||||
const DepGraphEditor = ({depGraph, picklists, editValues, onFieldValueUpdate, onClose}) => {
|
||||
let fieldComponents = {}
|
||||
let fieldLabels = {}
|
||||
depGraph.modalFields.map(
|
||||
(field) => {
|
||||
if (field.dataType == "Picklist") {
|
||||
fieldComponents[field.apiName] =
|
||||
<select
|
||||
key={'depGraph' + field.apiName}
|
||||
value={editValues[field.apiName].current}
|
||||
onChange={(event) => onFieldValueUpdate(
|
||||
field.apiName,
|
||||
event.target.value,
|
||||
editValues,
|
||||
picklists,
|
||||
depGraph.modalFields,
|
||||
depGraph.fieldTree)}>
|
||||
{ depGraph.picklistFields.get(field.apiName).map((picklistValue) => {
|
||||
return (
|
||||
<option
|
||||
key={picklistValue.value}
|
||||
value={picklistValue.value}>{picklistValue.label}</option>)
|
||||
})}
|
||||
</select>
|
||||
} else if (field.dataType == "Boolean" ) {
|
||||
fieldComponents[field.apiName] =
|
||||
<input
|
||||
type="checkbox"
|
||||
className="fieldEdit"
|
||||
key={'depGraph' + field.apiName}
|
||||
name={field.apiName}
|
||||
value={editValues[field.apiName].current}
|
||||
onChange={(event) => onFieldValueUpdate(
|
||||
field.apiName,
|
||||
new Boolean(!editValues[field.apiName].current).valueOf(),
|
||||
editValues,
|
||||
picklists,
|
||||
depGraph.modalFields,
|
||||
depGraph.fieldTree)}
|
||||
data-datatype={field.dataType}/>
|
||||
}
|
||||
|
||||
fieldLabels[field.apiName] = field.label
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div key="depGraphContainer">
|
||||
<ModalContainer >
|
||||
<ModalDialog>
|
||||
<div key="depGraphNodes">
|
||||
{ getEditorNodes(depGraph.fieldTree, fieldComponents, fieldLabels) }
|
||||
</div>
|
||||
<div className="slds-p-left--medium slds-p-top--small slds-p-botom--medium" key="depGraphCloseButtonDiv">
|
||||
<RecordButton key="depGraphCloseButton" label='Close' onClick={onClose} />
|
||||
</div>
|
||||
</ModalDialog>
|
||||
</ModalContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
DepGraphEditor.propTypes = {
|
||||
depGraph: PropTypes.object.isRequired,
|
||||
picklists: PropTypes.object.isRequired,
|
||||
editValues: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onFieldValueUpdate: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default DepGraphEditor
|
|
@ -2,47 +2,68 @@ import React, { PropTypes } from 'react'
|
|||
|
||||
import RecordSection from './RecordSection'
|
||||
import RecordButton from './RecordButton'
|
||||
import DepGraphEditor from './DepGraphEditor'
|
||||
|
||||
// Component that displays a Record.
|
||||
const Record = ({creds, error, layoutMode, uiMode, recordView, picklists, onBackClick, onCloneClick, onDeleteClick, onEditClick, onSaveClick, onSaveNewClick, onFieldValueUpdate, onFetchPicklist}) => {
|
||||
const Record = ({creds, error, layoutMode, uiMode, prevMode, recordView, picklists, depGraph, onBackClick, onCloneClick, onDeleteClick, onEditClick, onSaveClick, onSaveNewClick, onFieldValueUpdate, onDepGraphFieldValueUpdate, onDepGraphClose, onEditDepGraph}) => {
|
||||
|
||||
let recordType = '012000000000000AAA'; // 'Master'
|
||||
if (recordView.record && recordView.record.recordTypeInfo) {
|
||||
recordType = recordView.record.recordTypeInfo.recordTypeId;
|
||||
}
|
||||
|
||||
|
||||
if (layoutMode == "Clone"){
|
||||
layoutMode = "Edit"
|
||||
}
|
||||
|
||||
// TODO: support layouts other than Full.
|
||||
let allowEdit = (uiMode !== 'View');
|
||||
return (
|
||||
<div>
|
||||
<table className="slds-table" style={{"width":"auto"}}>
|
||||
{recordView.layouts.Full[layoutMode].map((section, i) =>
|
||||
<RecordSection
|
||||
allowEdit={allowEdit}
|
||||
key={'section' + i}
|
||||
onFieldValueUpdate={onFieldValueUpdate}
|
||||
onFetchPicklist={(url) => onFetchPicklist(creds, url)}
|
||||
picklists={picklists}
|
||||
error={error}
|
||||
editValues={recordView.editValues}
|
||||
section={section}
|
||||
index={i} />
|
||||
)}
|
||||
</table>
|
||||
<div className="slds-p-left--medium slds-p-top--small slds-p-botom--medium">
|
||||
<RecordButton label='Back' onClick={onBackClick} />
|
||||
{ uiMode === 'View' &&
|
||||
<RecordButton label='Delete' onClick={() => onDeleteClick(creds, recordView.recordId)} />
|
||||
}
|
||||
{ uiMode === 'View' &&
|
||||
<RecordButton label='Edit' onClick={() => onEditClick(creds, recordView.recordId)} />
|
||||
}
|
||||
{ uiMode === 'View' &&
|
||||
<RecordButton label='Clone' onClick={() => onCloneClick(creds, recordView.recordId)} />
|
||||
}
|
||||
{ uiMode === 'Edit' &&
|
||||
<RecordButton label='Save' onClick={() => onSaveClick(creds, recordView.recordId, recordView.objectInfo, recordView.editValues)} />
|
||||
}
|
||||
{ (uiMode === 'Create' || uiMode === 'Clone') &&
|
||||
<RecordButton label='Save' onClick={() => onSaveNewClick(creds, recordView.apiName, recordView.objectInfo, recordView.editValues)} />
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
{uiMode === 'EditDepGraph' &&
|
||||
<DepGraphEditor
|
||||
depGraph={depGraph}
|
||||
picklists={picklists}
|
||||
editValues={recordView.editValues}
|
||||
onFieldValueUpdate={onDepGraphFieldValueUpdate}
|
||||
onClose={() => onDepGraphClose(prevMode)}/> }
|
||||
<table className="slds-table" style={{"width":"auto"}}>
|
||||
{recordView.layouts.Full[layoutMode].map((section, i) =>
|
||||
<RecordSection
|
||||
allowEdit={allowEdit}
|
||||
uiMode={uiMode}
|
||||
key={'section' + i}
|
||||
onFieldValueUpdate={onFieldValueUpdate}
|
||||
picklists={picklists}
|
||||
error={error}
|
||||
editValues={recordView.editValues}
|
||||
section={section}
|
||||
index={i}
|
||||
objectInfo={recordView.objectInfo}
|
||||
onEditDepGraph={onEditDepGraph}
|
||||
recordView={recordView}/>
|
||||
)}
|
||||
</table>
|
||||
<div className="slds-p-left--medium slds-p-top--small slds-p-botom--medium">
|
||||
<RecordButton label='Back' onClick={onBackClick} />
|
||||
{ uiMode === 'View' &&
|
||||
<RecordButton label='Delete' onClick={() => onDeleteClick(creds, recordView.recordId)} />
|
||||
}
|
||||
{ uiMode === 'View' &&
|
||||
<RecordButton label='Edit' onClick={() => onEditClick(creds, recordView.objectInfo.apiName, recordType)} />
|
||||
}
|
||||
{ uiMode === 'View' &&
|
||||
<RecordButton label='Clone' onClick={() => onCloneClick(creds, recordView.recordId, recordView.record.apiName, recordType)} />
|
||||
}
|
||||
{ uiMode === 'Edit' &&
|
||||
<RecordButton label='Save' onClick={() => onSaveClick(creds, recordView.recordId, recordView.objectInfo, recordView.editValues)} />
|
||||
}
|
||||
{ (uiMode === 'Create' || uiMode === 'Clone') &&
|
||||
<RecordButton label='Save' onClick={() => onSaveNewClick(creds, recordView.apiName, recordView.objectInfo, recordView.editValues)} />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
</div> )
|
||||
}
|
||||
|
||||
// layoutMode = the mode of the layout to use. for Clone this will be 'Edit'.
|
||||
|
@ -51,9 +72,11 @@ Record.propTypes = {
|
|||
error: PropTypes.object.isRequired,
|
||||
creds: PropTypes.object.isRequired,
|
||||
layoutMode: PropTypes.string.isRequired,
|
||||
prevMode: PropTypes.string,
|
||||
uiMode: PropTypes.string.isRequired,
|
||||
recordView: PropTypes.object.isRequired,
|
||||
picklists: PropTypes.object.isRequired,
|
||||
picklists : PropTypes.object,
|
||||
depGraph: PropTypes.object,
|
||||
onBackClick: PropTypes.func.isRequired,
|
||||
onCloneClick: PropTypes.func.isRequired,
|
||||
onDeleteClick: PropTypes.func.isRequired,
|
||||
|
@ -61,7 +84,9 @@ Record.propTypes = {
|
|||
onSaveClick: PropTypes.func.isRequired,
|
||||
onSaveNewClick: PropTypes.func.isRequired,
|
||||
onFieldValueUpdate: PropTypes.func.isRequired,
|
||||
onFetchPicklist: PropTypes.func.isRequired
|
||||
onDepGraphFieldValueUpdate: PropTypes.func.isRequired,
|
||||
onEditDepGraph: PropTypes.func.isRequired,
|
||||
onDepGraphClose: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default Record
|
||||
|
|
|
@ -46,33 +46,85 @@ const getViewItemCells = (item, itemLabel, nonEmptyItem) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getEditComponent = (component, picklists, onFetchPicklist, onFieldValueUpdate, editValues, itemLabel, i) => {
|
||||
if (component.picklistUrl) {
|
||||
let value = editValues[component.field].current;
|
||||
if (value == null) {
|
||||
value = "";
|
||||
}
|
||||
const getFlattenedTree = (objectInfo, fieldTree, rootField) => {
|
||||
const flattenedTree = []
|
||||
flattenedTree.push(objectInfo.fields[rootField])
|
||||
doFlattenTree(objectInfo, fieldTree, flattenedTree)
|
||||
return flattenedTree
|
||||
}
|
||||
|
||||
let picklistValues = [];
|
||||
let hasPicklistValues = false;
|
||||
if (picklists[component.picklistUrl]) {
|
||||
picklistValues = picklists[component.picklistUrl].values;
|
||||
hasPicklistValues = true;
|
||||
const doFlattenTree = (objectInfo, tree, flattenedTree) => {
|
||||
if (tree) {
|
||||
let treeKeys = Object.keys(tree)
|
||||
for (var treeKey in treeKeys){
|
||||
flattenedTree.push(objectInfo.fields[treeKeys[treeKey]])
|
||||
doFlattenTree(objectInfo, tree[treeKeys[treeKey]], flattenedTree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getEditComponent = (component, picklists, onFieldValueUpdate, editValues, itemLabel, i, objectInfo, onEditDepGraph, uiMode, recordView) => {
|
||||
let isPicklist = (component.fieldInfo && component.fieldInfo.dataType == 'Picklist');
|
||||
let currPicklistValue = "";
|
||||
let picklistValues = [];
|
||||
|
||||
if (isPicklist) {
|
||||
currPicklistValue = editValues[component.field].current;
|
||||
if (currPicklistValue == null) {
|
||||
currPicklistValue = "";
|
||||
}
|
||||
|
||||
if (picklists.fieldValues && picklists.fieldValues[component.field]) {
|
||||
picklistValues = picklists.fieldValues[component.field].values;
|
||||
} else {
|
||||
// picklist collection fetch has not completed yet, or the field is somehow missing from its results.
|
||||
// add the current value so that it shows nicely in the UI.
|
||||
picklistValues.push({value: component.value, label: component.displayValue})
|
||||
}
|
||||
}
|
||||
|
||||
if (objectInfo
|
||||
&& ((component.field && component.field in objectInfo.dependentFields)
|
||||
|| (component.fieldInfo && component.fieldInfo.controllingFields.length > 0))) {
|
||||
// last field in controlling fields is the root field
|
||||
let lastControllingIndex = component.fieldInfo.controllingFields.length - 1
|
||||
let rootField = null
|
||||
if (component.field in objectInfo.dependentFields){
|
||||
rootField = component.field
|
||||
} else {
|
||||
rootField = component.fieldInfo.controllingFields[lastControllingIndex]
|
||||
}
|
||||
|
||||
// retrieve the picklist fields that need to show up
|
||||
const subfieldTree = objectInfo.dependentFields[rootField]
|
||||
const modalFields = getFlattenedTree(objectInfo, subfieldTree, rootField)
|
||||
|
||||
// open a modal on click of input field
|
||||
let fieldTree = {}
|
||||
fieldTree[rootField] = subfieldTree
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label
|
||||
key={'componentInput' + itemLabel + ',' + i}
|
||||
onClick={(event) => onEditDepGraph(picklists, modalFields, editValues, fieldTree, uiMode.toString())}
|
||||
value={currPicklistValue}>
|
||||
{editValues[component.field].current}
|
||||
</label>
|
||||
<button className="fa fa-pencil"
|
||||
key={'componentInput' + itemLabel + 'button,' + i}
|
||||
onClick={(event) => onEditDepGraph(picklists, modalFields, editValues, fieldTree, uiMode.toString())}>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isPicklist) {
|
||||
return (
|
||||
<select
|
||||
key={component.picklistUrl}
|
||||
value={value}
|
||||
onChange={(event) => onFieldValueUpdate(component.field, event.target.value)}
|
||||
onFocus={(event) => {
|
||||
if (!hasPicklistValues) {
|
||||
onFetchPicklist(component.picklistUrl);
|
||||
}
|
||||
}} >
|
||||
value={currPicklistValue}
|
||||
onChange={(event) => onFieldValueUpdate(component.field, event.target.value)}>
|
||||
{ picklistValues.map((picklistValue) => {
|
||||
return <option
|
||||
key={picklistValue.value}
|
||||
|
@ -86,10 +138,14 @@ const getEditComponent = (component, picklists, onFetchPicklist, onFieldValueUpd
|
|||
if (currentVal != null) {
|
||||
currentValStr = currentVal.toString();
|
||||
}
|
||||
|
||||
let componentType = 'text'
|
||||
if(component && component.fieldInfo.dataType == "Boolean") {
|
||||
componentType = 'checkbox'
|
||||
currentValStr = currentVal;
|
||||
}
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
type={componentType}
|
||||
className="fieldEdit"
|
||||
name={component.field}
|
||||
value={currentValStr}
|
||||
|
@ -101,7 +157,7 @@ const getEditComponent = (component, picklists, onFetchPicklist, onFieldValueUpd
|
|||
}
|
||||
}
|
||||
|
||||
const getEditItemCells = (item, picklists, onFetchPicklist, onFieldValueUpdate, error, editValues, itemLabel, nonEmptyItem) => {
|
||||
const getEditItemCells = (item, picklists, onFieldValueUpdate, error, editValues, itemLabel, nonEmptyItem, objectInfo, onEditDepGraph, uiMode, recordView) => {
|
||||
return (
|
||||
<td className="slds-cell-wrap" key={'editItemCell' + itemLabel} style={{"maxWidth":"350px"}}>
|
||||
{ item.customLinkUrl &&
|
||||
|
@ -119,7 +175,7 @@ const getEditItemCells = (item, picklists, onFetchPicklist, onFieldValueUpdate,
|
|||
<span className="slds-required">*</span>
|
||||
}
|
||||
{ component.editableForUpdate &&
|
||||
getEditComponent(component, picklists, onFetchPicklist, onFieldValueUpdate, editValues, itemLabel, i)
|
||||
getEditComponent(component, picklists, onFieldValueUpdate, editValues, itemLabel, i, objectInfo, onEditDepGraph, uiMode, recordView)
|
||||
}
|
||||
{ !component.editableForUpdate &&
|
||||
<label key={'component' + itemLabel + ',' + i}>{component.displayValue}</label>
|
||||
|
@ -144,8 +200,7 @@ const getEditItemCells = (item, picklists, onFetchPicklist, onFieldValueUpdate,
|
|||
// Component that displays a Record row. May include multiple items. JSX doesn't allow us to return
|
||||
// fragments made up of multiple <td>s without wrapping them in a single element so we can't break out
|
||||
// RecordItem into a separate component.
|
||||
const RecordRow = ({row, allowEdit, error, editValues, picklists, onFetchPicklist, onFieldValueUpdate, sectionIndex, rowIndex}) => {
|
||||
|
||||
const RecordRow = ({row, allowEdit, error, editValues, picklists, onFieldValueUpdate, sectionIndex, rowIndex, objectInfo, onEditDepGraph, uiMode, recordView}) => {
|
||||
let rowLabel = sectionIndex + ',' + rowIndex;
|
||||
|
||||
return (
|
||||
|
@ -156,7 +211,7 @@ const RecordRow = ({row, allowEdit, error, editValues, picklists, onFetchPicklis
|
|||
if (!allowEdit) {
|
||||
return getViewItemCells(item, itemLabel, nonEmptyItem);
|
||||
} else {
|
||||
return getEditItemCells(item, picklists, onFetchPicklist, onFieldValueUpdate, error, editValues, itemLabel, nonEmptyItem);
|
||||
return getEditItemCells(item, picklists, onFieldValueUpdate, error, editValues, itemLabel, nonEmptyItem, objectInfo, onEditDepGraph, uiMode, recordView);
|
||||
}
|
||||
})}
|
||||
</tr>
|
||||
|
@ -168,11 +223,14 @@ RecordRow.propTypes = {
|
|||
error: PropTypes.object.isRequired,
|
||||
allowEdit: PropTypes.bool.isRequired,
|
||||
editValues: PropTypes.object.isRequired,
|
||||
picklists: PropTypes.object.isRequired,
|
||||
picklists: PropTypes.object,
|
||||
sectionIndex: PropTypes.number.isRequired,
|
||||
rowIndex: PropTypes.number.isRequired,
|
||||
onFieldValueUpdate: PropTypes.func.isRequired,
|
||||
onFetchPicklist: PropTypes.func.isRequired
|
||||
objectInfo: PropTypes.object.isRequired,
|
||||
onEditDepGraph: PropTypes.func.isRequired,
|
||||
uiMode: PropTypes.string.isRequired,
|
||||
recordView: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default RecordRow
|
||||
export default RecordRow
|
|
@ -4,7 +4,7 @@ import ReactDOM from 'react-dom';
|
|||
import RecordRow from './RecordRow';
|
||||
|
||||
// Component that displays a Record section.
|
||||
const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdate, onFetchPicklist, allowEdit, index}) => {
|
||||
const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdate, allowEdit, index, objectInfo, onEditDepGraph, uiMode, recordView}) => {
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
|
@ -19,14 +19,17 @@ const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdat
|
|||
<RecordRow
|
||||
key={'sectionRow' + index + ',' + i}
|
||||
allowEdit={allowEdit}
|
||||
uiMode={uiMode}
|
||||
picklists={picklists}
|
||||
onFieldValueUpdate={onFieldValueUpdate}
|
||||
onFetchPicklist={onFetchPicklist}
|
||||
error={error}
|
||||
editValues={editValues}
|
||||
row={row}
|
||||
sectionIndex={index}
|
||||
rowIndex={i} />
|
||||
rowIndex={i}
|
||||
objectInfo={objectInfo}
|
||||
recordView={recordView}
|
||||
onEditDepGraph={onEditDepGraph}/>
|
||||
)}
|
||||
</tbody>
|
||||
);
|
||||
|
@ -35,12 +38,15 @@ const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdat
|
|||
RecordSection.propTypes = {
|
||||
error: PropTypes.object.isRequired,
|
||||
section: PropTypes.object.isRequired,
|
||||
objectInfo: PropTypes.object.isRequired,
|
||||
editValues: PropTypes.object.isRequired,
|
||||
picklists: PropTypes.object.isRequired,
|
||||
picklists: PropTypes.object,
|
||||
uiMode: PropTypes.string.isRequired,
|
||||
allowEdit: PropTypes.bool.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
onFieldValueUpdate: PropTypes.func.isRequired,
|
||||
onFetchPicklist: PropTypes.func.isRequired
|
||||
onEditDepGraph: PropTypes.func.isRequired,
|
||||
recordView: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default RecordSection
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { PropTypes } from 'react'
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import Inspector from 'react-json-inspector';
|
||||
import AsyncKickoff from '../containers/AsyncKickoff'
|
||||
import CreateableEntitiesList from './CreateableEntitiesList'
|
||||
import RecentItemList from './RecentItemList'
|
||||
|
@ -29,7 +29,7 @@ let getFooter = (screen, error, rawjson) => {
|
|||
{ errorNode }
|
||||
<RecordButton key="showJsonButton" label='Show JSON' onClick={() => $(".raw").toggle()} />
|
||||
<div className="raw" style={{"display":"none"}}>
|
||||
<JSONPretty key="rawjson" json={rawjson} />
|
||||
<Inspector key="rawjson" data={rawjson} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -38,7 +38,7 @@ let getFooter = (screen, error, rawjson) => {
|
|||
<div className="slds-p-left--medium slds-p-top--small slds-p-botom--medium">
|
||||
<RecordButton key="showJsonButton" label='Show JSON' onClick={() => $(".raw").toggle()} />
|
||||
<div className="raw" style={{"display":"none"}}>
|
||||
<JSONPretty key="rawjson" json={rawjson} />
|
||||
<Inspector key="rawjson" data={rawjson} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -46,18 +46,27 @@ let getFooter = (screen, error, rawjson) => {
|
|||
}
|
||||
|
||||
// Component that displays login / recent items / record screens.
|
||||
const RecordViewer = ({screen, updateEntities, updateItems, creds, error, record, recordId, rawjson, mode, picklists, entities, recentItems, onNewRecordClick, onCloneClick, onRecordClick, onBackClick, onDeleteClick, onEditClick, onSaveClick, onSaveNewClick, onFieldValueUpdate, onFetchEntities, onFetchPicklist, onFetchRecord, onFetchRecentItems}) => {
|
||||
const RecordViewer = ({screen, updateEntities, updateItems, creds, error, record, recordId, rawjson, mode, picklists, depGraph, entities, recentItems, onNewRecordClick, onCloneClick, onRecordClick, onBackClick, onDeleteClick, onEditClick, onSaveClick, onSaveNewClick, onFieldValueUpdate, onDepGraphFieldValueUpdate, onFetchEntities, onFetchRecord, onFetchRecentItems, onEditDepGraph, onDepGraphClose, prevMode}) => {
|
||||
if (screen == 'RECORD') {
|
||||
let layoutMode = (mode === 'Clone') ? 'Edit' : mode;
|
||||
|
||||
let layoutMode;
|
||||
if (mode === 'EditDepGraph') {
|
||||
layoutMode = prevMode;
|
||||
} else if (mode === 'Clone') {
|
||||
layoutMode = 'Edit';
|
||||
} else {
|
||||
layoutMode = mode;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Record recordView={record}
|
||||
layoutMode={layoutMode}
|
||||
uiMode={mode}
|
||||
prevMode={prevMode}
|
||||
creds={creds}
|
||||
error={error}
|
||||
picklists={picklists}
|
||||
depGraph={depGraph}
|
||||
onBackClick={onBackClick}
|
||||
onCloneClick={onCloneClick}
|
||||
onDeleteClick={onDeleteClick}
|
||||
|
@ -65,7 +74,9 @@ const RecordViewer = ({screen, updateEntities, updateItems, creds, error, recor
|
|||
onSaveClick={onSaveClick}
|
||||
onSaveNewClick={onSaveNewClick}
|
||||
onFieldValueUpdate={onFieldValueUpdate}
|
||||
onFetchPicklist={onFetchPicklist}/>
|
||||
onEditDepGraph={onEditDepGraph}
|
||||
onDepGraphFieldValueUpdate={onDepGraphFieldValueUpdate}
|
||||
onDepGraphClose={onDepGraphClose} />
|
||||
{ getFooter(screen, error, rawjson) }
|
||||
</div>
|
||||
);
|
||||
|
@ -125,6 +136,7 @@ RecordViewer.propTypes = {
|
|||
entities: PropTypes.object,
|
||||
recentItems: PropTypes.object,
|
||||
picklists: PropTypes.object,
|
||||
depGraph: PropTypes.object,
|
||||
onRecordClick: PropTypes.func.isRequired,
|
||||
onBackClick: PropTypes.func.isRequired,
|
||||
onCloneClick: PropTypes.func.isRequired,
|
||||
|
@ -136,8 +148,10 @@ RecordViewer.propTypes = {
|
|||
onFetchEntities: PropTypes.func.isRequired,
|
||||
onFetchRecentItems: PropTypes.func.isRequired,
|
||||
onFieldValueUpdate: PropTypes.func.isRequired,
|
||||
onFetchPicklist: PropTypes.func.isRequired,
|
||||
onFetchRecord: PropTypes.func.isRequired
|
||||
onDepGraphFieldValueUpdate: PropTypes.func.isRequired,
|
||||
onDepGraphClose: PropTypes.func.isRequired,
|
||||
onFetchRecord: PropTypes.func.isRequired,
|
||||
onEditDepGraph: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default RecordViewer
|
||||
|
|
|
@ -12,8 +12,10 @@ const mapStateToProps = (state) => {
|
|||
screen: 'RECORD',
|
||||
record: state.record.record,
|
||||
mode: state.record.mode,
|
||||
prevMode: state.record.prevMode,
|
||||
creds: state.login,
|
||||
picklists: state.picklists,
|
||||
picklists : state.picklists,
|
||||
depGraph: state.depGraph,
|
||||
rawjson: state.rawjson,
|
||||
error: state.error
|
||||
}
|
||||
|
@ -46,9 +48,6 @@ const mapStateToProps = (state) => {
|
|||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
onCloneClick: (creds, id) => {
|
||||
dispatch(actions.fetchCloneDefaults(creds, id))
|
||||
},
|
||||
onFetchRecord: (creds, id) => {
|
||||
dispatch(actions.fetchRecord(creds, id))
|
||||
},
|
||||
|
@ -57,15 +56,19 @@ const mapDispatchToProps = (dispatch) => {
|
|||
},
|
||||
onNewRecordClick: (creds, apiName) => {
|
||||
dispatch(actions.fetchCreateDefaults(creds, apiName))
|
||||
// hacky: default record type for now
|
||||
dispatch(actions.fetchPicklists(creds, apiName, '012000000000000AAA'))
|
||||
},
|
||||
onCloneClick: (creds, id) => {
|
||||
onCloneClick: (creds, id, apiName, recordType) => {
|
||||
dispatch(actions.fetchCloneDefaults(creds, id))
|
||||
dispatch(actions.fetchPicklists(creds, apiName, recordType))
|
||||
},
|
||||
onDeleteClick: (creds, id) => {
|
||||
dispatch(actions.deleteRecord(creds, id))
|
||||
},
|
||||
onEditClick: () => {
|
||||
dispatch(actions.editRecord())
|
||||
onEditClick: (creds, apiName, recordType) => {
|
||||
dispatch(actions.editRecord(creds, apiName, recordType))
|
||||
dispatch(actions.fetchPicklists(creds, apiName, recordType))
|
||||
},
|
||||
onSaveClick: (creds, id, objectInfo, editValues) => {
|
||||
dispatch(actions.saveRecord(creds, id, objectInfo, editValues))
|
||||
|
@ -85,8 +88,14 @@ const mapDispatchToProps = (dispatch) => {
|
|||
onFieldValueUpdate: (field, value) => {
|
||||
dispatch(actions.updateFieldValue(field, value))
|
||||
},
|
||||
onFetchPicklist: (creds, url) => {
|
||||
dispatch(actions.fetchPicklist(creds, url));
|
||||
onEditDepGraph: (picklists, modalFields, editValues, fieldTree, prevMode) => {
|
||||
dispatch(actions.editDepGraph(picklists, modalFields, editValues, fieldTree, prevMode));
|
||||
},
|
||||
onDepGraphFieldValueUpdate: (field, value, picklists, modalFields, rootField, fieldTree) => {
|
||||
dispatch(actions.updateDepGraphFieldValue(field, value, picklists, modalFields, rootField, fieldTree))
|
||||
},
|
||||
onDepGraphClose: (mode) => {
|
||||
dispatch(actions.closeDepGraph(mode))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
function getParentToChildPicklistValues(picklists, modalFields, editValues){
|
||||
// create a new map to fill in
|
||||
const picklistCollectionMap = new Map()
|
||||
for (let modalFieldIndex in modalFields){
|
||||
const field = modalFields[modalFieldIndex]
|
||||
const picklistValue = picklists.fieldValues[modalFields[modalFieldIndex].apiName]
|
||||
const individualValueObjects = getLegalValues(picklists, field, editValues)
|
||||
picklistCollectionMap.set(field.apiName,individualValueObjects)
|
||||
}
|
||||
return picklistCollectionMap
|
||||
}
|
||||
|
||||
function getLegalValues(picklists, field, editValues){
|
||||
const picklistValue = picklists.fieldValues[field.apiName]
|
||||
if (picklistValue){
|
||||
if (isEmpty(picklistValue.controllerValues)){
|
||||
return picklistValue.values.map(v => ({value: v.value, label: v.label}));
|
||||
} else {
|
||||
|
||||
// fill in picklistFields
|
||||
const individualValueObjects = [];
|
||||
const parentField = field.controllerName
|
||||
let parentFieldValue = editValues[parentField].current
|
||||
|
||||
if (parentFieldValue != null){
|
||||
parentFieldValue = parentFieldValue.toString()
|
||||
}
|
||||
let validForIndex = -1
|
||||
if (picklistValue.controllerValues[parentFieldValue] != null){
|
||||
validForIndex = picklistValue.controllerValues[parentFieldValue]
|
||||
}
|
||||
picklistValue.values.forEach((individualValue) => {
|
||||
if (isInArray(validForIndex,individualValue.validFor)){
|
||||
individualValueObjects.push({
|
||||
value: individualValue.value,
|
||||
label: individualValue.label
|
||||
});
|
||||
}
|
||||
});
|
||||
return individualValueObjects
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function isEmpty(obj) {
|
||||
for(var key in obj) {
|
||||
if(obj.hasOwnProperty(key))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isInArray(value, array) {
|
||||
return array.indexOf(value) > -1;
|
||||
}
|
||||
|
||||
export default { getParentToChildPicklistValues, getLegalValues }
|
|
@ -1,220 +1,220 @@
|
|||
function getLayoutItemModel(objectInfo, record, recordType, item) {
|
||||
|
||||
var result = {};
|
||||
result.label = item.label;
|
||||
|
||||
var values = [];
|
||||
var linkId;
|
||||
var linkText;
|
||||
var customLinkUrl;
|
||||
var customText;
|
||||
|
||||
(item.layoutComponents || []).forEach(function (component) {
|
||||
|
||||
let picklistUrl = undefined;
|
||||
|
||||
// Component display info.
|
||||
if (component.componentType == 'Field') {
|
||||
var compValue = component.apiName;
|
||||
var fieldInfo = objectInfo.fields[compValue];
|
||||
|
||||
// Picklist value URL.
|
||||
if (fieldInfo && fieldInfo.dataType == 'Picklist') {
|
||||
picklistUrl = '/services/data/v41.0/ui-api/object-info/' + objectInfo.apiName + '/picklist-values/' + recordType + '/' + fieldInfo.apiName;
|
||||
}
|
||||
|
||||
// Reference link.
|
||||
if (fieldInfo && fieldInfo.reference) {
|
||||
// The relationship may be absent if it amounts to null.
|
||||
if (record.fields[fieldInfo.relationshipName]) {
|
||||
var relatedData = record.fields[fieldInfo.relationshipName].value
|
||||
if (relatedData) {
|
||||
linkId = relatedData.fields.Id.value
|
||||
linkText = relatedData.fields.Name.value
|
||||
|
||||
var result = {};
|
||||
result.label = item.label;
|
||||
|
||||
var values = [];
|
||||
var linkId;
|
||||
var linkText;
|
||||
var customLinkUrl;
|
||||
var customText;
|
||||
|
||||
(item.layoutComponents || []).forEach(function (component) {
|
||||
|
||||
// Component display info.
|
||||
if (component.componentType == 'Field') {
|
||||
var compValue = component.apiName;
|
||||
var fieldInfo = objectInfo.fields[compValue];
|
||||
|
||||
// Reference link.
|
||||
if (fieldInfo && fieldInfo.reference) {
|
||||
// The relationship may be absent if it amounts to null.
|
||||
if (record.fields[fieldInfo.relationshipName]) {
|
||||
var relatedData = record.fields[fieldInfo.relationshipName].value
|
||||
if (relatedData) {
|
||||
linkId = relatedData.fields.Id.value
|
||||
linkText = relatedData.fields.Name.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldInfo && (fieldInfo.type == 'Datetime' || fieldInfo.type == 'DateOnly')) {
|
||||
var currValue = record.fields[compValue].value;
|
||||
var formattedData = new Date(currValue);
|
||||
values.push(
|
||||
{displayValue: formattedData,
|
||||
value: currValue,
|
||||
label:component.label,
|
||||
field:compValue,
|
||||
fieldInfo,
|
||||
picklistUrl,
|
||||
editableForNew:item.editableForNew,
|
||||
editableForUpdate:item.editableForUpdate,
|
||||
isNull:currValue == null});
|
||||
} else if (record.fields[compValue]) {
|
||||
var displayValue = record.fields[compValue].displayValue;
|
||||
let rawValue = record.fields[compValue].value;
|
||||
if (displayValue == null && rawValue != null) {
|
||||
displayValue = rawValue.toString();
|
||||
}
|
||||
values.push(
|
||||
{displayValue: displayValue,
|
||||
value: rawValue,
|
||||
label:component.label,
|
||||
field:compValue,
|
||||
fieldInfo,
|
||||
picklistUrl,
|
||||
editableForNew:item.editableForNew,
|
||||
editableForUpdate:item.editableForUpdate,
|
||||
isNull:displayValue == null})
|
||||
} else {
|
||||
console.log('Missing expected field: ' + compValue);
|
||||
}
|
||||
|
||||
} else if (component.componentType == 'CustomLink') {
|
||||
customLinkUrl = component.customLinkUrl;
|
||||
linkText = component.label;
|
||||
} else if (component.componentType == 'Canvas') {
|
||||
customText = 'Canvas: ' + component.apiName;
|
||||
} else if (component.componentType == 'EmptySpace') {
|
||||
customText = '';
|
||||
} else if (component.componentType == 'VisualforcePage') {
|
||||
customText = 'VF Page: ' + component.apiName;
|
||||
} else if (component.componentType == 'ReportChart') {
|
||||
customText = 'Report Chart: ' + component.apiName;
|
||||
}
|
||||
});
|
||||
|
||||
result.values = values;
|
||||
result.linkId = linkId;
|
||||
result.linkText = linkText;
|
||||
result.customLinkUrl = customLinkUrl;
|
||||
result.customText = customText;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getLayoutRowModel(objectInfo, record, recordType, itemsIn) {
|
||||
var items = [];
|
||||
|
||||
(itemsIn || []).forEach(function (item) {
|
||||
items.push(getLayoutItemModel(objectInfo, record, recordType, item));
|
||||
});
|
||||
|
||||
var result = {
|
||||
items: items
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
function getLayoutSectionModel(objectInfo, record, recordType, section) {
|
||||
var result = {};
|
||||
result.heading = section.heading;
|
||||
result.useHeading = (section.useHeading) ? section.useHeading : false;
|
||||
|
||||
var rows = [];
|
||||
(section.layoutRows || []).forEach(function (row) {
|
||||
rows.push(getLayoutRowModel(objectInfo, record, recordType, row.layoutItems));
|
||||
});
|
||||
|
||||
result.rows = rows;
|
||||
return result;
|
||||
}
|
||||
|
||||
function getLayoutModelForDefaults(defaults) {
|
||||
let objectInfo = defaults.objectInfo;
|
||||
let record = defaults.record;
|
||||
let layout = defaults.layout;
|
||||
|
||||
let recordType = '012000000000000AAA'; // 'Master'
|
||||
if (record.recordTypeInfo) {
|
||||
recordType = record.recordTypeInfo.recordTypeId;
|
||||
}
|
||||
|
||||
let layouts = {};
|
||||
let editValues = {};
|
||||
|
||||
try {
|
||||
let modeType = layout.mode;
|
||||
let layoutType = layout.layoutType;
|
||||
|
||||
layouts[layoutType] = {};
|
||||
|
||||
const sections = layout.sections.map((section) => getLayoutSectionModel(objectInfo, record, recordType, section));
|
||||
sections.forEach((section) =>
|
||||
section.rows.forEach((row) =>
|
||||
row.items.forEach((item) =>
|
||||
item.values.forEach((value) =>
|
||||
editValues[value.field] = {
|
||||
original: value.value,
|
||||
current: value.value
|
||||
}))));
|
||||
layouts[layoutType][modeType] = sections;
|
||||
|
||||
let result = {
|
||||
layouts,
|
||||
editValues,
|
||||
objectInfo,
|
||||
apiName: objectInfo.apiName
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
console.log('ERROR CREATING DEFAULTS LAYOUT MODEL ' + err);
|
||||
return {layouts: [], editValues: {}, objectInfo:{}, apiName:null};
|
||||
}
|
||||
}
|
||||
|
||||
function getLayoutModel(recordId, recordView) {
|
||||
let record = recordView.records[recordId];
|
||||
let apiName = record.apiName;
|
||||
let entityEntry = recordView.layouts[apiName];
|
||||
let objectInfo = recordView.objectInfos[apiName];
|
||||
|
||||
let recordType = '012000000000000AAA'; // 'Master'
|
||||
if (record.recordTypeInfo) {
|
||||
recordType = record.recordTypeInfo.recordTypeId;
|
||||
}
|
||||
|
||||
let layouts = {};
|
||||
let editValues = {};
|
||||
|
||||
try {
|
||||
let recordTypeRep = entityEntry[Object.keys(entityEntry)[0]]; // TODO: support multiple record types.
|
||||
for (const layoutType of Object.keys(recordTypeRep)) {
|
||||
let layoutTypeRep = recordTypeRep[layoutType];
|
||||
for (const modeType of Object.keys(layoutTypeRep)) {
|
||||
let layoutRep = layoutTypeRep[modeType];
|
||||
if (!layouts[layoutType]) {
|
||||
layouts[layoutType] = {};
|
||||
if (fieldInfo && (fieldInfo.type == 'Datetime' || fieldInfo.type == 'DateOnly')) {
|
||||
var currValue = record.fields[compValue].value;
|
||||
var formattedData = new Date(currValue);
|
||||
values.push(
|
||||
{displayValue: formattedData,
|
||||
value: currValue,
|
||||
label:component.label,
|
||||
field:compValue,
|
||||
fieldInfo,
|
||||
editableForNew:item.editableForNew,
|
||||
editableForUpdate:item.editableForUpdate,
|
||||
isNull:currValue == null});
|
||||
} else if (record.fields[compValue]) {
|
||||
var displayValue = record.fields[compValue].displayValue;
|
||||
let rawValue = record.fields[compValue].value;
|
||||
if (displayValue == null && rawValue != null) {
|
||||
displayValue = rawValue.toString();
|
||||
}
|
||||
values.push(
|
||||
{displayValue: displayValue,
|
||||
value: rawValue,
|
||||
label:component.label,
|
||||
field:compValue,
|
||||
fieldInfo,
|
||||
editableForNew:item.editableForNew,
|
||||
editableForUpdate:item.editableForUpdate,
|
||||
isNull:displayValue == null})
|
||||
} else {
|
||||
console.log('Missing expected field: ' + compValue);
|
||||
}
|
||||
|
||||
const sections = layoutRep.sections.map((section) => getLayoutSectionModel(objectInfo, record, recordType, section));
|
||||
|
||||
if (modeType === 'Edit') {
|
||||
sections.forEach((section) =>
|
||||
section.rows.forEach((row) =>
|
||||
row.items.forEach((item) =>
|
||||
item.values.forEach((value) =>
|
||||
editValues[value.field] = {
|
||||
original: value.value,
|
||||
current: value.value}
|
||||
))));
|
||||
}
|
||||
|
||||
layouts[layoutType][modeType] = sections;
|
||||
|
||||
} else if (component.componentType == 'CustomLink') {
|
||||
customLinkUrl = component.customLinkUrl;
|
||||
linkText = component.label;
|
||||
} else if (component.componentType == 'Canvas') {
|
||||
customText = 'Canvas: ' + component.apiName;
|
||||
} else if (component.componentType == 'EmptySpace') {
|
||||
customText = '';
|
||||
} else if (component.componentType == 'VisualforcePage') {
|
||||
customText = 'VF Page: ' + component.apiName;
|
||||
} else if (component.componentType == 'ReportChart') {
|
||||
customText = 'Report Chart: ' + component.apiName;
|
||||
}
|
||||
}
|
||||
|
||||
let result = {
|
||||
layouts,
|
||||
editValues,
|
||||
objectInfo,
|
||||
recordId: record.id
|
||||
});
|
||||
|
||||
result.values = values;
|
||||
result.linkId = linkId;
|
||||
result.linkText = linkText;
|
||||
result.customLinkUrl = customLinkUrl;
|
||||
result.customText = customText;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getLayoutRowModel(objectInfo, record, recordType, itemsIn) {
|
||||
var items = [];
|
||||
|
||||
(itemsIn || []).forEach(function (item) {
|
||||
items.push(getLayoutItemModel(objectInfo, record, recordType, item));
|
||||
});
|
||||
|
||||
var result = {
|
||||
items: items
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
console.log('ERROR CREATING LAYOUT MODEL ' + err);
|
||||
return {layouts: [], editValues: {}, objectInfo:{}, recordId: null};
|
||||
}
|
||||
}
|
||||
|
||||
export default {getLayoutItemModel, getLayoutRowModel, getLayoutSectionModel, getLayoutModel, getLayoutModelForDefaults}
|
||||
|
||||
function getLayoutSectionModel(objectInfo, record, recordType, section) {
|
||||
var result = {};
|
||||
result.heading = section.heading;
|
||||
result.useHeading = (section.useHeading) ? section.useHeading : false;
|
||||
|
||||
var rows = [];
|
||||
(section.layoutRows || []).forEach(function (row) {
|
||||
rows.push(getLayoutRowModel(objectInfo, record, recordType, row.layoutItems));
|
||||
});
|
||||
|
||||
result.rows = rows;
|
||||
return result;
|
||||
}
|
||||
|
||||
function getLayoutModelForDefaults(defaults) {
|
||||
let objectInfo = {}
|
||||
if (defaults.objectInfo) {
|
||||
// in case of clone
|
||||
objectInfo = defaults.objectInfo
|
||||
} else {
|
||||
// in case of create
|
||||
objectInfo = defaults.objectInfos[defaults.record.apiName];
|
||||
}
|
||||
let record = defaults.record;
|
||||
let layout = defaults.layout;
|
||||
|
||||
let recordType = '012000000000000AAA'; // 'Master'
|
||||
if (record.recordTypeInfo) {
|
||||
recordType = record.recordTypeInfo.recordTypeId;
|
||||
}
|
||||
|
||||
let layouts = {};
|
||||
let editValues = {};
|
||||
|
||||
try {
|
||||
let modeType = layout.mode;
|
||||
let layoutType = layout.layoutType;
|
||||
|
||||
layouts[layoutType] = {};
|
||||
|
||||
const sections = layout.sections.map((section) => getLayoutSectionModel(objectInfo, record, recordType, section));
|
||||
sections.forEach((section) =>
|
||||
section.rows.forEach((row) =>
|
||||
row.items.forEach((item) =>
|
||||
item.values.forEach((value) =>
|
||||
editValues[value.field] = {
|
||||
original: value.value,
|
||||
current: value.value
|
||||
}))));
|
||||
layouts[layoutType][modeType] = sections;
|
||||
|
||||
let result = {
|
||||
layouts,
|
||||
editValues,
|
||||
objectInfo,
|
||||
apiName: objectInfo.apiName
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
console.log('ERROR CREATING DEFAULTS LAYOUT MODEL ' + err);
|
||||
return {layouts: [], editValues: {}, objectInfo:{}, apiName:null};
|
||||
}
|
||||
}
|
||||
|
||||
function getLayoutModel(recordId, recordView) {
|
||||
let record = recordView.records[recordId];
|
||||
let apiName = record.apiName;
|
||||
let entityEntry = recordView.layouts[apiName];
|
||||
let objectInfo = recordView.objectInfos[apiName];
|
||||
|
||||
let recordType = '012000000000000AAA'; // 'Master'
|
||||
if (record.recordTypeInfo) {
|
||||
recordType = record.recordTypeInfo.recordTypeId;
|
||||
}
|
||||
|
||||
let layouts = {};
|
||||
let editValues = {};
|
||||
|
||||
try {
|
||||
let recordTypeRep = entityEntry[Object.keys(entityEntry)[0]]; // TODO: support multiple record types.
|
||||
for (const layoutType of Object.keys(recordTypeRep)) {
|
||||
let layoutTypeRep = recordTypeRep[layoutType];
|
||||
for (const modeType of Object.keys(layoutTypeRep)) {
|
||||
let layoutRep = layoutTypeRep[modeType];
|
||||
if (!layouts[layoutType]) {
|
||||
layouts[layoutType] = {};
|
||||
}
|
||||
|
||||
const sections = layoutRep.sections.map((section) => getLayoutSectionModel(objectInfo, record, recordType, section));
|
||||
|
||||
if (modeType === 'Edit') {
|
||||
sections.forEach((section) =>
|
||||
section.rows.forEach((row) =>
|
||||
row.items.forEach((item) =>
|
||||
item.values.forEach((value) =>
|
||||
editValues[value.field] = {
|
||||
original: value.value,
|
||||
current: value.value}
|
||||
))));
|
||||
}
|
||||
|
||||
layouts[layoutType][modeType] = sections;
|
||||
}
|
||||
}
|
||||
|
||||
let result = {
|
||||
layouts,
|
||||
editValues,
|
||||
objectInfo,
|
||||
recordId: record.id,
|
||||
record
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
console.log('ERROR CREATING LAYOUT MODEL ' + err);
|
||||
return {layouts: [], editValues: {}, objectInfo:{}, recordId: null, record:null};
|
||||
}
|
||||
}
|
||||
|
||||
export default {getLayoutItemModel, getLayoutRowModel, getLayoutSectionModel, getLayoutModel, getLayoutModelForDefaults}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import depGraphHelper from '../helpers/depGraphHelper'
|
||||
const depGraph = (state = {pickListFields: undefined, modalFields: undefined, fieldTree: undefined}, action) => {
|
||||
switch (action.type) {
|
||||
case 'EDIT_DEP_GRAPH':
|
||||
return {
|
||||
picklistFields : depGraphHelper.getParentToChildPicklistValues(
|
||||
action.picklists,
|
||||
action.modalFields,
|
||||
action.editValues),
|
||||
modalFields : action.modalFields,
|
||||
fieldTree : action.fieldTree
|
||||
}
|
||||
case 'UPDATE_PICKLIST_FIELDS':
|
||||
return {
|
||||
...state,
|
||||
picklistFields : new Map([...state.picklistFields, ...action.picklistFields])
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default depGraph
|
|
@ -2,7 +2,7 @@ const entities = (state = {sobjects: []}, action) => {
|
|||
switch (action.type) {
|
||||
case 'RECEIVE_ENTITIES':
|
||||
return {
|
||||
sobjects: action.entities.sobjects,
|
||||
sobjects: action.entities.objects,
|
||||
receivedAt: action.receivedAt
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -3,10 +3,11 @@ import { combineReducers } from 'redux'
|
|||
import login from './login'
|
||||
import recentitems from './recentitems'
|
||||
import record from './record'
|
||||
import picklists from './picklists'
|
||||
import entities from './entities'
|
||||
import rawjson from './rawjson'
|
||||
import error from './error'
|
||||
import picklists from './picklists.js'
|
||||
import depGraph from './depGraph.js'
|
||||
|
||||
export default combineReducers( {
|
||||
login,
|
||||
|
@ -15,5 +16,6 @@ export default combineReducers( {
|
|||
picklists,
|
||||
entities,
|
||||
rawjson,
|
||||
error
|
||||
error,
|
||||
depGraph
|
||||
})
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
const picklists = (state = {}, action) => {
|
||||
const picklists = (state = {values: undefined}, action) => {
|
||||
switch (action.type) {
|
||||
case 'RECEIVE_PICKLIST':
|
||||
case 'RECEIVE_PICKLISTS':
|
||||
return {
|
||||
...state,
|
||||
[action.url]: action.result
|
||||
fieldValues : action.result.picklistFieldValues
|
||||
}
|
||||
case 'FETCH_PICKLISTS': // clear when new collection is requested
|
||||
return {
|
||||
fieldValues: undefined
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default picklists
|
||||
export default picklists
|
|
@ -10,6 +10,8 @@ const rawjson = (state = null, action) => {
|
|||
return action.defaults;
|
||||
case 'RECEIVE_CREATE_DEFAULTS':
|
||||
return action.defaults;
|
||||
case 'RECEIVE_PICKLISTS':
|
||||
return action.result;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import recordLayout from '../helpers/recordLayout'
|
||||
|
||||
const record = (state = {record: undefined, mode: 'View'}, action) => {
|
||||
const record = (state = {record: undefined, mode: 'View', prevMode: undefined}, action) => {
|
||||
switch (action.type) {
|
||||
case 'RECEIVE_RECORD':
|
||||
return {
|
||||
|
@ -22,6 +22,18 @@ const record = (state = {record: undefined, mode: 'View'}, action) => {
|
|||
...state,
|
||||
mode: 'Edit'
|
||||
}
|
||||
case 'EDIT_DEP_GRAPH':
|
||||
return {
|
||||
...state,
|
||||
prevMode: action.prevMode, // save previous mode to return to on close
|
||||
mode: 'EditDepGraph'
|
||||
}
|
||||
case 'CLOSE_DEP_GRAPH':
|
||||
return {
|
||||
...state,
|
||||
prevMode: undefined,
|
||||
mode: action.mode
|
||||
}
|
||||
case 'CLEAR_RECORD':
|
||||
return {
|
||||
record: undefined
|
||||
|
|
|
@ -5,7 +5,7 @@ import { receiveCloneDefaults } from '../actions'
|
|||
export default function* cloneDefaultsFetcher (action) {
|
||||
|
||||
// TODO: Add dynamic FormFactor.
|
||||
let defaultsUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/record-defaults/clone/' + action.id + '?formFactor=Large';
|
||||
let defaultsUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/record-defaults/clone/' + action.id + '?formFactor=Large';
|
||||
|
||||
let req = {
|
||||
method: 'GET',
|
||||
|
|
|
@ -5,7 +5,7 @@ import { receiveCreateDefaults, showError } from '../actions'
|
|||
export default function* createDefaultsFetcher (action) {
|
||||
|
||||
// TODO: Add dynamic FormFactor.
|
||||
let defaultsUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/record-defaults/create/' + action.apiName + '?formFactor=Large';
|
||||
let defaultsUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/record-defaults/create/' + action.apiName + '?formFactor=Large';
|
||||
|
||||
let req = {
|
||||
method: 'GET',
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import { call, put } from 'redux-saga/effects'
|
||||
import depGraphHelper from '../helpers/depGraphHelper'
|
||||
import { updateFieldValue, updatePicklistFields, showError } from '../actions'
|
||||
|
||||
export default function* depGraphValueUpdater (action) {
|
||||
try {
|
||||
|
||||
// make a deep copy to not mess with the real state.
|
||||
const editValues = JSON.parse(JSON.stringify(action.editValues));
|
||||
|
||||
const modalFieldsMap = {}
|
||||
for (var modalField of action.modalFields){
|
||||
modalFieldsMap[modalField.apiName] = modalField
|
||||
}
|
||||
|
||||
// traverse down to the target field in the tree.
|
||||
const field = modalFieldsMap[action.field];
|
||||
var treeNode = action.fieldTree;
|
||||
for (var i = field.controllingFields.length -1; i>=0; i--) {
|
||||
var controllingField = field.controllingFields[i]
|
||||
treeNode = treeNode[controllingField];
|
||||
}
|
||||
|
||||
treeNode = treeNode[action.field];
|
||||
|
||||
// now treeNode is the sub-tree rooted at the target field
|
||||
|
||||
// do simple update of this field
|
||||
yield put(updateFieldValue(action.field, action.value));
|
||||
editValues[action.field].current = action.value
|
||||
|
||||
//queue
|
||||
var queue = []
|
||||
queue.push(treeNode)
|
||||
while(queue.length > 0){
|
||||
var currentTreeNode = queue.shift()
|
||||
for (const childField of Object.keys(currentTreeNode)) {
|
||||
const childNode = currentTreeNode[childField];
|
||||
const currentField = modalFieldsMap[childField];
|
||||
|
||||
// update the legal values for this field.
|
||||
const legalValues = depGraphHelper.getLegalValues(action.picklists, currentField, editValues);
|
||||
const legalValuesByField = new Map();
|
||||
legalValuesByField.set(childField,legalValues);
|
||||
yield put(updatePicklistFields(legalValuesByField));
|
||||
|
||||
// make sure the current value is legal.
|
||||
const currentValue = editValues[currentField.apiName].current;
|
||||
|
||||
var found = false;
|
||||
if (legalValues) { // if no legalValues, checkbox, all values legal.
|
||||
if (legalValues == []) {
|
||||
yield put(updateFieldValue(currentField.apiName, null));
|
||||
editValues[currentField.apiName].current = null
|
||||
queue.push(childNode)
|
||||
} else {
|
||||
legalValues.forEach(legalValue => {
|
||||
if (legalValue.value == currentValue) {
|
||||
found = true
|
||||
}
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
yield put(updateFieldValue(currentField.apiName, legalValues[0].value));
|
||||
editValues[currentField.apiName].current = legalValues[0].value
|
||||
queue.push(childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
console.error('Create defaults fetch error: ' + JSON.stringify(err))
|
||||
}
|
||||
}
|
|
@ -4,12 +4,13 @@ import { receiveEntities } from '../actions'
|
|||
|
||||
export default function* entitiesFetcher(action) {
|
||||
|
||||
let mruUrl = action.creds.instanceUrl + '/services/data/v41.0/sobjects'
|
||||
let mruUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/object-info'
|
||||
|
||||
let req = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization' : 'Bearer ' + action.creds.accessToken}
|
||||
'Authorization' : 'Bearer ' + action.creds.accessToken,
|
||||
'X-Chatter-Entity-Encoding': false}
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import { call, put } from 'redux-saga/effects'
|
||||
|
||||
import { receivePicklist } from '../actions'
|
||||
|
||||
export default function* picklistFetcher (action) {
|
||||
let picklistValuesUrl = action.creds.instanceUrl + action.url;
|
||||
|
||||
let req = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization' : 'Bearer ' + action.creds.accessToken,
|
||||
'X-Chatter-Entity-Encoding': false}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = yield call(fetch, picklistValuesUrl, req)
|
||||
const responseJson = yield response.json()
|
||||
yield put(receivePicklist(action.url, responseJson))
|
||||
} catch(err) {
|
||||
console.error('Picklist fetch error: ' + err + ' error keys: ' + Object.keys(err));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { call, put } from 'redux-saga/effects'
|
||||
|
||||
import { receivePicklists } from '../actions'
|
||||
|
||||
export default function* picklistsFetcher (action) {
|
||||
|
||||
let url = action.creds.instanceUrl + '/services/data/v42.0/ui-api/object-info/' + action.apiName + '/picklist-values/' + action.recordType + '/';
|
||||
let req = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization' : 'Bearer ' + action.creds.accessToken,
|
||||
'X-Chatter-Entity-Encoding': false}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = yield call(fetch, url, req)
|
||||
const responseJson = yield response.json()
|
||||
yield put(receivePicklists(url, responseJson))
|
||||
} catch(err) {
|
||||
console.error('Error in fetching picklist values collection: ' + JSON.stringify(err))
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import { receiveRecentItems } from '../actions'
|
|||
|
||||
export default function* recentItemsFetcher(action) {
|
||||
|
||||
let mruUrl = action.creds.instanceUrl + '/services/data/v41.0/recent'
|
||||
let mruUrl = action.creds.instanceUrl + '/services/data/v42.0/recent'
|
||||
|
||||
let req = {
|
||||
method: 'GET',
|
||||
|
|
|
@ -4,7 +4,7 @@ import { recordCreateSuccess, showError } from '../actions'
|
|||
|
||||
export default function* recordCreator (action) {
|
||||
|
||||
let recordDataUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/records/';
|
||||
let recordDataUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/records/';
|
||||
|
||||
var recordInput = {};
|
||||
recordInput.apiName = action.apiName;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { finishedRecordDelete } from '../actions'
|
|||
|
||||
export default function* recordDeleter (action) {
|
||||
|
||||
let recordUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/records/' + action.recordId
|
||||
let recordUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/records/' + action.recordId
|
||||
let req = {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { receiveRecord } from '../actions'
|
|||
export default function* recordFetcher (action) {
|
||||
|
||||
// TODO: Add FormFactor & LayoutType to the state so that we can change view based on them, instead of hardcoding only Large here.
|
||||
let recordViewUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/record-ui/' + action.recordId + '?formFactor=Large&modes=View,Edit';
|
||||
let recordViewUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/record-ui/' + action.recordId + '?formFactor=Large&modes=View,Edit';
|
||||
let req = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { recordUpdateSuccess, showError } from '../actions'
|
|||
|
||||
export default function* recordUpdater (action) {
|
||||
|
||||
let recordDataUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/records/' + action.recordId;
|
||||
let recordDataUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/records/' + action.recordId;
|
||||
|
||||
var recordInput = {};
|
||||
recordInput.fields = {};
|
||||
|
|
|
@ -4,20 +4,22 @@ import recentItemsFetcher from './recentItemsFetcher'
|
|||
import recordFetcher from './recordFetcher'
|
||||
import recordDeleter from './recordDeleter'
|
||||
import recordUpdater from './recordUpdater'
|
||||
import picklistFetcher from './picklistFetcher'
|
||||
import entitiesFetcher from './entitiesFetcher'
|
||||
import recordCreator from './recordCreator'
|
||||
import cloneDefaultsFetcher from './cloneDefaultsFetcher'
|
||||
import createDefaultsFetcher from './createDefaultsFetcher'
|
||||
import picklistsFetcher from './picklistsFetcher'
|
||||
import depGraphValueUpdater from './depGraphValueUpdater'
|
||||
|
||||
export default function* rootSaga() {
|
||||
yield takeEvery('FETCH_RECORD', recordFetcher)
|
||||
yield takeEvery('FETCH_RECENT_ITEMS', recentItemsFetcher)
|
||||
yield takeEvery('DELETE_RECORD', recordDeleter)
|
||||
yield takeEvery('SAVE_RECORD', recordUpdater)
|
||||
yield takeEvery('FETCH_PICKLIST', picklistFetcher)
|
||||
yield takeEvery('FETCH_ENTITIES', entitiesFetcher)
|
||||
yield takeEvery('CREATE_RECORD', recordCreator)
|
||||
yield takeEvery('FETCH_CREATE_DEFAULTS', createDefaultsFetcher);
|
||||
yield takeEvery('FETCH_CLONE_DEFAULTS', cloneDefaultsFetcher);
|
||||
yield takeEvery('FETCH_PICKLISTS', picklistsFetcher);
|
||||
yield takeEvery('UPDATE_DEP_GRAPH_FIELD_VALUE', depGraphValueUpdater)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
web:
|
||||
build: .
|
||||
command: 'bash -c ''node app.js'''
|
||||
working_dir: /app/user
|
||||
working_dir: /usr/src/app
|
||||
environment:
|
||||
PORT: 8050
|
||||
NGINX_PORT: 8443
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
.json-inspector,
|
||||
.json-inspector__selection {
|
||||
font: 14px/1.4 Consolas, monospace;
|
||||
}
|
||||
|
||||
.json-inspector__leaf {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.json-inspector__line {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.json-inspector__line:after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -200px;
|
||||
right: -50px;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.json-inspector__line:hover:after {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.json-inspector__leaf_composite > .json-inspector__line {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.json-inspector__radio,
|
||||
.json-inspector__flatpath {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.json-inspector__value {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.json-inspector__search {
|
||||
min-width: 300px;
|
||||
margin: 0 10px 10px 0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.json-inspector__key {
|
||||
color: #505050;
|
||||
}
|
||||
|
||||
.json-inspector__value_helper,
|
||||
.json-inspector__value_null,
|
||||
.json-inspector__not-found {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
|
||||
.json-inspector__value_string {
|
||||
color: #798953;
|
||||
}
|
||||
|
||||
.json-inspector__value_boolean {
|
||||
color: #75b5aa;
|
||||
}
|
||||
|
||||
.json-inspector__value_number {
|
||||
color: #d28445;
|
||||
}
|
||||
|
||||
.json-inspector__hl {
|
||||
background: #ff0;
|
||||
box-shadow: 0 -1px 0 2px #ff0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.json-inspector__show-original {
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.json-inspector__show-original:hover {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.json-inspector__show-original:before {
|
||||
content: '⥂';
|
||||
}
|
||||
|
||||
.json-inspector__show-original:hover:after {
|
||||
content: ' expand'
|
||||
}
|
23
package.json
23
package.json
|
@ -9,23 +9,26 @@
|
|||
"url": "https://github.com/forcedotcom/RecordViewer.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@salesforce-ux/design-system": "2.1.2",
|
||||
"body-parser": "1.13.3",
|
||||
"cookie-parser": "1.3.5",
|
||||
"express": "4.13.3",
|
||||
"express-session": "1.11.3",
|
||||
"jade": "1.11.0",
|
||||
"morgan": "1.6.1",
|
||||
"os-homedir": "1.0.1",
|
||||
"@salesforce-ux/design-system": "^2.1.2",
|
||||
"body-parser": "^1.13.3",
|
||||
"cookie-parser": "^1.3.5",
|
||||
"express": "^4.13.3",
|
||||
"express-session": "^1.11.3",
|
||||
"morgan": "^1.6.1",
|
||||
"os-homedir": "^1.0.1",
|
||||
"pug": "^2.0.1",
|
||||
"q": "0.8.12",
|
||||
"react": "^15.4.0",
|
||||
"react-bootstrap": "0.13.3",
|
||||
"react-dom": "^15.4.0",
|
||||
"react-json-inspector": "7.1.0",
|
||||
"react-json-pretty": "^1.2.1",
|
||||
"react-modal-dialog": "4.0.7",
|
||||
"react-redux": "^4.4.6",
|
||||
"redux": "^3.6.0",
|
||||
"redux-saga": "^0.13.0",
|
||||
"serve-static": "1.10.0",
|
||||
"stylus": "0.52.0",
|
||||
"serve-static": "^1.10.0",
|
||||
"stylus": "^0.52.4",
|
||||
"xml2js": "0.4.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -8,6 +8,7 @@ html
|
|||
|
||||
link(rel='stylesheet', href='/libs/salesforce-ux/design-system/styles/salesforce-lightning-design-system.min.css')
|
||||
link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css')
|
||||
link(rel='stylesheet', href='json-inspector.css')
|
||||
|
||||
script(type='text/javascript', src='https://code.jquery.com/jquery-2.0.3.min.js')
|
||||
script(type='text/javascript', src='root.js')
|
Загрузка…
Ссылка в новой задаче