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 all 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 @@ -73,6 +73,7 @@ DONE Embeddable rxmarbles

DONE Initial affordance animation in diagram
TODO Fix affordance animation stuck for combineLatest
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
11,567 changes: 11,275 additions & 292 deletions dist/js/app.js

Large diffs are not rendered by default.

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
10 changes: 7 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,9 @@ 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),
showGhost$: properties.get('ghost').startWith(false)
};
}

Expand Down
189 changes: 137 additions & 52 deletions src/components/diagram/diagram-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,98 +9,181 @@ 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) {
return h('x-marble.diagramMarble', {
key: `marble${marbleData.get('id')}`,
data: marbleData,
isDraggable,
style: {size: diagramMarbleSize}
});
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, showGhost, isGhost) {
if (!isGhost || showGhost) {
return h('x-marble.diagramMarble', {
key: `marble${marbleData.get('id')}`,
data: marbleData,
isDraggable,
style: {size: diagramMarbleSize},
isGhost
});
}
}

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

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

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 (and is not draggable)
if (endTime === undefined || endTime > 100 || (!isDraggable && 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, showGhost) {
/* 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;

if (showGhost) {
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 (!showGhost || (start < end)) {
sections.push(h('div.diagramArrow', {
style: mergeStyles(arrowStyle, {
backgroundColor: diagramArrowColor,
left: middleStart,
right: middleEnd
})
}));
}

return sections;
}

function renderDiagramArrowHead() {
function renderDiagramArrowHead(data, isCompact, showGhost) {
let end = data.get('end');
let isGhost = end < 100;
let color = (showGhost && 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, showGhost) {
let marblesVTree = data.get('notifications')
.map(notification => renderMarble(notification, isInteractive))
.map(notification => renderMarble(notification, isInteractive, showGhost, 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, showGhost),
renderDiagramArrowHead(data, isCompact, showGhost),
h('div', {style: diagramBodyStyle(isCompact)}, renderEndpoints(data, isInteractive, showGhost).concat(marblesVTree))
])
}

Expand Down Expand Up @@ -149,6 +232,8 @@ function diagramView(model) {
vtree$: Rx.Observable.combineLatest(
animateData$(model.data$).merge(model.newData$),
model.isInteractive$,
model.isCompact$,
model.showGhost$,
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