Skip to content
This repository has been archived by the owner on Oct 26, 2021. It is now read-only.

Track and show individual subscriptions to input streams #18

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ DONE Embeddable rxmarbles
>>> v1.3.0

DONE Initial affordance animation in diagram
DONE Track and show individual subscriptions to input streams
TODO Disambiguate simultaneous marbles
Vertically spread them
Change example takeLast(1) to takeLast(2)
Expand Down
2 changes: 1 addition & 1 deletion src/app-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function vrenderContent(route) {
position: 'absolute',
top: '0'},
pageRowLastChildStyle)}
,h('x-sandbox', {key: 'sandbox', route: route, width: '820px'})
,h('x-sandbox', {key: 'sandbox', route: route, width: '820px', showSubscriptions: true})
)
]
);
Expand Down
9 changes: 6 additions & 3 deletions src/components/diagram/diagram-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ function applyChangeMarbleTime(diagramData, marbleDelta) {
}

function applyChangeEndTime(diagramData, endDelta) {
var newEnd = diagramData.get('end') + endDelta;
return diagramData
.set('end', diagramData.get('end') + endDelta);
.set('end', newEnd)
.set('eventualEnd', newEnd);
}

function applyMarbleDataConstraints(marbleData) {
Expand All @@ -46,7 +48,7 @@ function applyEndTimeConstraint(diagramData) {
newEndTime = Math.round(newEndTime);
newEndTime = Math.min(newEndTime, 100);
newEndTime = Math.max(0, newEndTime);
return diagramData.set('end', newEndTime);
return diagramData.set('end', newEndTime).set('eventualEnd', newEndTime);
}

function applyDiagramDataConstraints(diagramData) {
Expand Down Expand Up @@ -96,7 +98,8 @@ function diagramModel(properties, intent) {
intent.changeEndTime$,
properties.get('interactive')
),
isInteractive$: properties.get('interactive').startWith(false)
isInteractive$: properties.get('interactive').startWith(false),
isCompact$: properties.get('compact').startWith(false)
};
}

Expand Down
174 changes: 127 additions & 47 deletions src/components/diagram/diagram-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,98 +9,177 @@ let h = Cycle.h;

const MARBLE_WIDTH = 5; // estimate of a marble width, in percentages
const diagramSidePadding = Dimens.spaceMedium;
const diagramVerticalMargin = Dimens.spaceLarge;
const diagramArrowThickness = '2px';
const diagramArrowSidePadding = Dimens.spaceLarge;
const diagramArrowHeadSize = '8px';
const diagramArrowColor = Colors.black;
const diagramArrowColorGhost = Colors.almostWhite;
const diagramMarbleSize = Dimens.spaceLarge;
const diagramCompletionHeight = '44px';

const diagramStyle = mergeStyles({
position: 'relative',
display: 'block',
width: '100%',
height: `calc(${diagramMarbleSize} + 2 * ${diagramVerticalMargin})`,
overflow: 'visible',
cursor: 'default'},
textUnselectable
);

const diagramBodyStyle = {
position: 'absolute',
left: `calc(${diagramArrowSidePadding} + ${diagramSidePadding}
+ (${diagramMarbleSize} / 2))`,
right: `calc(${diagramArrowSidePadding} + ${diagramSidePadding}
+ (${diagramMarbleSize} / 2))`,
top: `calc(${diagramVerticalMargin} + (${diagramMarbleSize} / 2))`,
height: diagramCompletionHeight,
marginTop: `calc(0px - (${diagramCompletionHeight} / 2))`
};

function renderMarble(marbleData, isDraggable = false) {
function diagramVerticalMargin(isCompact) {
return isCompact ? Dimens.spaceSmall : Dimens.spaceLarge;
}

function diagramStyle(isCompact) {
return mergeStyles({
position: 'relative',
display: 'block',
width: '100%',
height: `calc(${diagramMarbleSize} + 2 * ${diagramVerticalMargin(isCompact)})`,
overflow: 'visible',
cursor: 'default'
},
textUnselectable
);
}

const paddingToTimeline = `(${diagramArrowSidePadding} + ${diagramSidePadding} + (${diagramMarbleSize} / 2))`;
const timelineSize = `(100% - (2 * ${paddingToTimeline}))`;
function timeLeftPosition(time) {
return `(${paddingToTimeline} + (${timelineSize} * ${time / 100}))`;
}
function timeRightPosition(time) {
return `(${paddingToTimeline} + (${timelineSize} * ${(100 - time) / 100}))`;
}

function diagramBodyStyle(isCompact) {
return {
position: 'absolute',
left: `calc(${paddingToTimeline})`,
right: `calc(${paddingToTimeline})`,
top: `calc(${diagramVerticalMargin(isCompact)} + (${diagramMarbleSize} / 2))`,
height: diagramCompletionHeight,
marginTop: `calc(0px - (${diagramCompletionHeight} / 2))`
};
}

function renderMarble(marbleData, isDraggable = false, isGhost = false) {
return h('x-marble.diagramMarble', {
key: `marble${marbleData.get('id')}`,
data: marbleData,
isDraggable,
style: {size: diagramMarbleSize}
style: {size: diagramMarbleSize},
isGhost
});
}

function renderCompletion(diagramData, isDraggable = false) {
let endTime = diagramData.get('end');
function renderEndpoints(diagramData, isDraggable = false) {
var endpoints = [
renderEndpoint(diagramData, 'start', 'diagramStart', false, false),
renderEndpoint(diagramData, 'end', 'diagramCompletion', isDraggable, false)
];

// add the eventualEndpoint if it is past the actyal end
if (diagramData.get('eventualEnd') > diagramData.get('end')) {
endpoints.push(renderEndpoint(diagramData, 'eventualEnd', 'diagramEventualEnd', false, true));
}

return endpoints;
}

function renderEndpoint(diagramData, timeName, endpointType, isDraggable, isGhost) {
let endTime = diagramData.get(timeName);
// do not render if the time is not defined, or it was at the end of our simulation
if (endTime === undefined || endTime >= 100) {
return undefined;
}

let color = isGhost ? diagramArrowColorGhost : diagramArrowColor;

let isTall = diagramData.get('notifications').some(marbleData =>
Math.abs(marbleData.get('time') - diagramData.get('end')) <= MARBLE_WIDTH*0.5
Math.abs(marbleData.get('time') - endTime) <= MARBLE_WIDTH*0.5
);
return h('x-diagram-completion.diagramCompletion', {
key: 'completion',
return h('x-diagram-completion.' + endpointType, {
key: endpointType,
time: endTime,
isDraggable,
isTall,
style: {
thickness: diagramArrowThickness,
color: diagramArrowColor,
color: color,
height: diagramCompletionHeight
}
});
}


function renderDiagramArrow() {
return h('div.diagramArrow', {style: {
backgroundColor: diagramArrowColor,
function renderDiagramArrow(data, isCompact) {
/* render the line in 3 segments:
* - to the left of 'start' render ghosted
* - render between start & end normal
* - to the right of 'end' render ghosted
*/
const arrowStyle = {
height: diagramArrowThickness,
position: 'absolute',
top: `calc(${diagramVerticalMargin} + (${diagramMarbleSize} / 2))`,
left: diagramSidePadding,
right: diagramSidePadding
}});
top: `calc(${diagramVerticalMargin(isCompact)} + (${diagramMarbleSize} / 2))`
};
let sections = [];
let start = data.get('start');
let end = data.get('end');
let middleStart = diagramSidePadding;
let middleEnd = diagramSidePadding;

sections.push(h('div.diagramArrow', {
style: mergeStyles(arrowStyle, {
backgroundColor: diagramArrowColorGhost,
left: middleStart,
right: `calc(${timeRightPosition(start)})`
})
}));
middleStart = `calc(${timeLeftPosition(start)})`;

if (end < 100) {
sections.push(h('div.diagramArrow', {
style: mergeStyles(arrowStyle, {
backgroundColor: diagramArrowColorGhost,
left: `calc(${timeLeftPosition(end)})`,
right: middleEnd
})
}));
middleEnd = `calc(${timeRightPosition(end)})`;
}

if (start < end) {
sections.push(h('div.diagramArrow', {
style: mergeStyles(arrowStyle, {
backgroundColor: diagramArrowColor,
left: middleStart,
right: middleEnd
})
}));
}

return sections;
}

function renderDiagramArrowHead() {
function renderDiagramArrowHead(data, isCompact) {
let end = data.get('end');
let isGhost = end < 100;
let color = isGhost ? diagramArrowColorGhost : diagramArrowColor;
return h('div.diagramArrowHead', {style: {
width: 0,
height: 0,
borderTop: `${diagramArrowHeadSize} solid transparent`,
borderBottom: `${diagramArrowHeadSize} solid transparent`,
borderLeft: `calc(2 * ${diagramArrowHeadSize}) solid ${diagramArrowColor}`,
borderLeft: `calc(2 * ${diagramArrowHeadSize}) solid ${color}`,
display: 'inline-block',
right: `calc(${diagramSidePadding} - 1px)`,
position: 'absolute',
top: `calc(${diagramVerticalMargin} + (${diagramMarbleSize} / 2)
top: `calc(${diagramVerticalMargin(isCompact)} + (${diagramMarbleSize} / 2)
- ${diagramArrowHeadSize} + (${diagramArrowThickness} / 2))`
}});
}

function renderDiagram(data, isInteractive) {
function renderDiagram(data, isInteractive, isCompact) {
let marblesVTree = data.get('notifications')
.map(notification => renderMarble(notification, isInteractive))
.map(notification => renderMarble(notification, isInteractive, notification.get('time') > (data.get('end') + 0.01)))
.toArray(); // from Immutable.List
let completionVTree = renderCompletion(data, isInteractive);
return h('div', {style: diagramStyle}, [
renderDiagramArrow(),
renderDiagramArrowHead(),
h('div', {style: diagramBodyStyle}, [completionVTree].concat(marblesVTree))
return h('div', {style: diagramStyle(isCompact)}, [
renderDiagramArrow(data, isCompact),
renderDiagramArrowHead(data, isCompact),
h('div', {style: diagramBodyStyle(isCompact)}, renderEndpoints(data, isInteractive).concat(marblesVTree))
])
}

Expand Down Expand Up @@ -149,6 +228,7 @@ function diagramView(model) {
vtree$: Rx.Observable.combineLatest(
animateData$(model.data$).merge(model.newData$),
model.isInteractive$,
model.isCompact$,
renderDiagram
)
};
Expand Down
41 changes: 24 additions & 17 deletions src/components/marble.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ function createContainerStyle(inputStyle) {
};
}

function renderSvg(data, isDraggable, inputStyle, isHighlighted) {
function renderSvg(data, isDraggable, inputStyle, isHighlighted, isGhost) {
let POSSIBLE_COLORS = [Colors.blue, Colors.green, Colors.yellow, Colors.red];
let color = POSSIBLE_COLORS[data.get('id') % POSSIBLE_COLORS.length];
let color = isGhost ? Colors.almostWhite : POSSIBLE_COLORS[data.get('id') % POSSIBLE_COLORS.length];
return svg('svg.marbleShape', {
style: mergeStyles({
overflow: 'visible',
Expand All @@ -31,7 +31,7 @@ function renderSvg(data, isDraggable, inputStyle, isHighlighted) {
[
svg('circle', {
style: {
stroke: Colors.black,
stroke: isGhost ? Colors.greyLight : Colors.black,
fill: color
},
attributes: {
Expand All @@ -43,21 +43,27 @@ function renderSvg(data, isDraggable, inputStyle, isHighlighted) {
);
}

function renderInnerContent(data, inputStyle) {
function renderInnerContent(data, inputStyle, isGhost) {
var style = {
position: 'absolute',
width: '100%',
height: '100%',
top: '0',
margin: '0',
textAlign: 'center',
lineHeight: inputStyle.size
};

if (isGhost) {
style['color'] = Colors.greyLight;
}

return h('p.marbleContent', {
style: mergeStyles({
position: 'absolute',
width: '100%',
height: '100%',
top: '0',
margin: '0',
textAlign: 'center',
lineHeight: inputStyle.size},
textUnselectable)
style: mergeStyles(style, textUnselectable)
}, `${data.get('content')}`);
}

function render(data, isDraggable, inputStyle, isHighlighted) {
function render(data, isDraggable, inputStyle, isHighlighted, isGhost) {
let draggableContainerStyle = {
cursor: 'ew-resize'
};
Expand All @@ -69,8 +75,8 @@ function render(data, isDraggable, inputStyle, isHighlighted) {
isDraggable ? draggableContainerStyle : null),
attributes: {'data-marble-id': data.get('id')}
},[
renderSvg(data, isDraggable, inputStyle, isHighlighted),
renderInnerContent(data, inputStyle)
renderSvg(data, isDraggable, inputStyle, isHighlighted, isGhost),
renderInnerContent(data, inputStyle, isGhost)
]);
}

Expand All @@ -79,6 +85,7 @@ function marbleComponent(interactions, properties) {
let stopHighlight$ = interactions.get('.marbleRoot', 'mouseleave');
let data$ = properties.get('data');
let isDraggable$ = properties.get('isDraggable').startWith(false);
let isGhost$ = properties.get('isGhost').startWith(false);
let style$ = properties.get('style').startWith({});
let isHighlighted$ = Rx.Observable.merge(
startHighlight$.map(() => true),
Expand All @@ -87,7 +94,7 @@ function marbleComponent(interactions, properties) {

return {
vtree$: Rx.Observable.combineLatest(
data$, isDraggable$, style$, isHighlighted$, render
data$, isDraggable$, style$, isHighlighted$, isGhost$, render
)
};
}
Expand Down
6 changes: 4 additions & 2 deletions src/components/sandbox/sandbox-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ function prepareNotification(input, diagramId) {

function prepareInputDiagram(diagram, indexInDiagramArray = 0) {
let last = diagram[diagram.length - 1];
return Immutable.Map({})
let end = (typeof last === 'number') ? last : 100;
return Immutable.Map({ start: 0 })
.set('notifications', getNotifications(diagram)
.map(notification => prepareNotification(notification, indexInDiagramArray))
)
.set('end', (typeof last === 'number') ? last : 100)
.set('end', end)
.set('eventualEnd', end)
.set('id', indexInDiagramArray);
}

Expand Down
Loading