js2flowchart
Version:
> Why? While I've been working on [Under-the-hood-ReactJS](https://github.com/Bogdan-Lyashenko/Under-the-hood-ReactJS) I spent enormous amount of time on creating schemes. Each change in code or flowchart affects all entire scheme instantly, forcing you t
276 lines (224 loc) • 7.21 kB
JavaScript
import escape from 'xml-escape';
import { mergeObjectStructures } from 'shared/utils/composition';
import {
generateId,
splitNameString,
getMaxStringLengthFromList,
getPathId
} from 'shared/utils/string';
import { flatTree } from 'shared/utils/flatten';
import { calculateShapesBoundaries } from 'shared/utils/geometry';
import {
MAX_NAME_STR_LENGTH,
getNameSplitterTokensIterator
} from '../appearance/TextContentConfigurator';
export const delegateInit = (shape, themeFieldName) => {
function init(node, position, theme) {
return shape(getInitialState(node, position, theme, themeFieldName));
}
init.getThemeFieldName = () => {
return themeFieldName;
};
return init;
};
export const getInitialState = (node, { x, y }, theme, type) => {
const nameParts = splitNameString(
node.name,
MAX_NAME_STR_LENGTH,
getNameSplitterTokensIterator()
),
totalNamePartsNumber = nameParts.length,
maxNamePartLength = getMaxStringLengthFromList(nameParts);
return {
id: generateId(),
nodePathId: getPathId(node),
type,
body: [],
theme,
originalTheme: theme,
node,
name: node.name,
prefixName: node.prefixName,
nameParts,
totalNamePartsNumber,
maxNamePartLength,
initialPosition: { x, y }
};
};
export const setupInitialProperties = state => ({
fromPoint: calculateFromPoint(state),
toPoint: calculateToPoint(state),
backPoint: calculateBackPoint(state),
childOffsetPoint: calculateChildOffsetPoint(state),
boundaries: calculateBoundaries(state)
});
export const extractBasicState = state => ({
...state,
position: calculatePosition(state),
dimensions: calculateDimensions(state)
});
export const setupInitialSelectors = state => ({
getBody() {
return state.body;
},
getBoundaries() {
return state.boundaries;
},
getBackPoint() {
return state.backPoint;
},
getAssignedConnectionArrow() {
return state.connectionArrow;
},
getChildOffsetPoint() {
return state.childOffsetPoint;
},
getDimensions() {
return state.dimensions;
},
getId() {
return state.id;
},
getFromPoint() {
return state.fromPoint;
},
getMargin() {
return state.theme.margin;
},
getName() {
return state.name;
},
getNode() {
return state.node;
},
getNodeType() {
return state.node.type;
},
getNodePathId() {
return state.nodePathId;
},
getNodeKey() {
return state.node.key;
},
getParent() {
return state.parent;
},
getPosition() {
return state.position;
},
getToPoint() {
return state.toPoint;
},
getShapeType() {
return state.type;
}
});
export const setupSharedPrint = state => ({
//TODO: fix spacing for multi line name
printName(newPosition) {
const { position, theme, nameParts } = state;
const { x, y } = newPosition ? newPosition : position;
const name = nameParts
.map(
(part, i) =>
`<tspan x="${x + theme.horizontalPadding}" y="${y +
2 * theme.verticalPadding * (i + 1)}">${escape(part)}</tspan>`
)
.join('');
//TODO: move to svg primitives
// 3 because of ellipsis 3 dots
return `${nameParts[0].length <= state.name.length + 3 ? `<title>${escape(state.name)}</title>` : ''}
<text x="${x + theme.horizontalPadding}" y="${y + 2 * theme.verticalPadding}"
font-family="${theme.fontFamily}" font-size="${theme.fontSize}" fill="${theme.textColor}">
${name}
</text>`;
},
printDebugInfo({ debug } = {}) {
if (!debug) return '';
const { position, dimensions, theme, nodePathId } = state;
return `<text x="${position.x + 3 * theme.horizontalPadding}" y="${position.y +
dimensions.h +
theme.verticalPadding}"
font-family="${theme.fontFamily}" font-size="${theme.debugFontSize}" fill="${theme.debugTextColor}">
${nodePathId}
</text>`;
}
});
export const setupGetChildBoundaries = state => ({
getChildBoundaries(filterFn) {
const { body, boundaries } = state;
if (!body.length) {
return boundaries;
}
const flattedTree = flatTree(
{
getBody: () => (filterFn ? body.filter(filterFn) : body),
getBoundaries: () => boundaries
},
node => node.getBody()
);
return calculateShapesBoundaries(flattedTree.map(item => item.getBoundaries()));
}
});
export const setupStateModifiers = state => ({
addChild(child) {
state.body.push(child);
},
setParent(parent) {
state.parent = parent;
},
connectChild(child) {
this.addChild(child);
child.setParent(this);
},
updateTheme(newTheme) {
state.theme = mergeObjectStructures(state.theme, newTheme);
},
assignConnectionArrow(connectionArrow) {
state.connectionArrow = connectionArrow;
}
});
export const setupBasicBehaviour = state =>
Object.assign(
{},
setupSharedPrint(state),
setupGetChildBoundaries(state),
setupStateModifiers(state)
);
export const setupCompleteState = initialState => {
let state = extractBasicState(initialState);
return { ...state, ...setupInitialProperties(state) };
};
export const calculateNameBasedWidth = ({ maxNamePartLength, theme }) =>
maxNamePartLength * theme.symbolWidth;
export const calculateNameBasedHeight = ({ totalNamePartsNumber, theme }) =>
totalNamePartsNumber * theme.symbolHeight + (totalNamePartsNumber - 1) * theme.lineHeight;
export const calculateWidth = state =>
2 * state.theme.horizontalPadding + calculateNameBasedWidth(state);
export const calculateHeight = state =>
2 * state.theme.verticalPadding + calculateNameBasedHeight(state);
export const calculateDimensions = state => ({
w: calculateWidth(state),
h: calculateHeight(state)
});
export const calculatePosition = state => ({ ...state.initialPosition });
export const calculateFromPoint = ({ position, dimensions, theme }) => ({
x: position.x + theme.childOffset / 2,
y: position.y + dimensions.h
});
export const calculateToPoint = ({ position, dimensions }) => ({
x: position.x,
y: position.y + dimensions.h / 2
});
export const calculateBackPoint = ({ position, dimensions }) => ({
x: position.x + dimensions.w,
y: position.y + dimensions.h / 2
});
export const calculateChildOffsetPoint = ({ theme, dimensions }) => ({
x: theme.childOffset,
y: dimensions.h + theme.childOffset / 2
});
export const calculateBoundaries = ({ position, dimensions }) => ({
min: { x: position.x, y: position.y },
max: { x: position.x + dimensions.w, y: position.y + dimensions.h }
});