Skip to content
Draft
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ release/

**/coveragereport/**/**
src/**/coverage.json
nohup.out
32 changes: 32 additions & 0 deletions src/Blashing.Shared/Pages/AdditionalWidgets.razor
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@
</div>
</div>

<div class="row">
<div class="col">

<h2>Timeline</h2>
<TimelineWidget
Title="Timeline"
Items="@(new List<TimelineItem> {
new("09:00", "Stand-up", "#E8F770"),
new("10:30", "Code Review", "#54C7EC"),
new("14:00", "Deploy", "#FF6B6B"),
new("16:30", "Retrospective", "#54EC7B")
})"
MoreInfo="Today"
UpdatedAtMessage="Last updated at 0:00">
</TimelineWidget>

</div>
</div>

<div class="row">
<div class="col">

Expand Down Expand Up @@ -65,5 +84,18 @@
BackgroundColor="#8fb347">
</CircleCIBuildStatusWidget>
</Element>
<Element Row="1" Column="1">
<TimelineWidget
Title="Timeline"
Items="@(new List<TimelineItem> {
new("09:00", "Stand-up", "#E8F770"),
new("10:30", "Code Review", "#54C7EC"),
new("14:00", "Deploy", "#FF6B6B"),
new("16:30", "Retrospective", "#54EC7B")
})"
MoreInfo="Today"
UpdatedAtMessage="Last updated at 0:00">
</TimelineWidget>
</Element>

</Dashboard>
1 change: 1 addition & 0 deletions src/Blashing.Shared/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
@using Blashing.Widgets;
@using Blashing.Widgets.ServerStatusSquares;
@using Blashing.Widgets.CircleCIBuildStatus;
@using Blashing.Widgets.Timeline;
27 changes: 27 additions & 0 deletions src/Blashing.Stories/Stories/TimelineWidget.stories.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@using Blashing.Widgets.Timeline

@attribute [Stories("Example/Timeline")]

<Stories TComponent="TimelineWidget">

<ArgType For="_ => _.Title" Control="ControlType.Default" />
<ArgType For="_ => _.MoreInfo" Control="ControlType.Default" />
<ArgType For="_ => _.UpdatedAtMessage" Control="ControlType.Default" />

<Story Name="Default">

<Arguments>
<Arg For="_ => _.Title" Value='"Timeline"' />
<Arg For="_ => _.MoreInfo" Value='"Today"' />
<Arg For="_ => _.UpdatedAtMessage" Value='@DateTime.Now.ToLongDateString()' />
<Arg For="_ => _.Items" Value='@(new List<TimelineItem> { new("09:00", "Stand-up"), new("10:30", "Code Review"), new("14:00", "Deploy"), new("16:30", "Retrospective") })' />
</Arguments>

<Template>
<TimelineWidget @attributes="context.Args">
</TimelineWidget>
</Template>

</Story>

</Stories>
64 changes: 64 additions & 0 deletions src/Blashing.Widgets.Tests/TimelineWidgetTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Bunit;
using Xunit;

using Blashing.Widgets.Timeline;

namespace Blashing.Widgets.Tests;

public class TimelineWidgetTest : BunitContext
{
[Fact]
public void TimelineWidgetMarkupShouldContainPassedInValues()
{
var title = "Timeline Title";
var moreInfo = "More Info";
var updatedAtMessage = "Updated At Message";
var items = new List<TimelineItem>()
{
new TimelineItem("10:00", "Event One"),
new TimelineItem("11:30", "Event Two")
};

var cut = Render<TimelineWidget>(parameters => parameters
.Add(p => p.Title, title)
.Add(p => p.MoreInfo, moreInfo)
.Add(p => p.UpdatedAtMessage, updatedAtMessage)
.Add(p => p.Items, items)
);

var expectedTitleMarkup = $"<h1 class=\"title\">{title}</h1>";
cut.FindAll("h1")[0].MarkupMatches(expectedTitleMarkup);

var expectedMoreInfoMarkup = $"<p class=\"more-info\">{moreInfo}</p>";
cut.FindAll("p")[0].MarkupMatches(expectedMoreInfoMarkup);

var expectedUpdatedAtMessageMarkup = $"<p class=\"updated-at\">{updatedAtMessage}</p>";
cut.FindAll("p")[1].MarkupMatches(expectedUpdatedAtMessageMarkup);
}

[Fact]
public void TimelineWidgetShouldContainPassedInValues()
{
var title = "Timeline Title";
var moreInfo = "More Info";
var updatedAtMessage = "Updated At Message";
var items = new List<TimelineItem>()
{
new TimelineItem("10:00", "Event One"),
new TimelineItem("11:30", "Event Two")
};

var cut = Render<TimelineWidget>(parameters => parameters
.Add(p => p.Title, title)
.Add(p => p.MoreInfo, moreInfo)
.Add(p => p.UpdatedAtMessage, updatedAtMessage)
.Add(p => p.Items, items)
);

var timelineWidget = cut.Instance;
Assert.Equal(timelineWidget.Title, title);
Assert.Equal(timelineWidget.MoreInfo, moreInfo);
Assert.Equal(timelineWidget.UpdatedAtMessage, updatedAtMessage);
Assert.Equal(timelineWidget.Items, items);
}
}
3 changes: 3 additions & 0 deletions src/Blashing.Widgets/Timeline/TimelineItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Blashing.Widgets.Timeline;

public record TimelineItem(string Date, string Title, string? Color = null);
19 changes: 19 additions & 0 deletions src/Blashing.Widgets/Timeline/TimelineWidget.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@inherits Blashing.Core.Components.BaseWidget;

<div class="widget-timeline" style="background-color:@BackgroundColor">
<h1 class="title">@Title</h1>

<ul class="timeline-list">
@foreach (var item in Items ?? Enumerable.Empty<TimelineItem>())
{
<li class="timeline-event">
<span class="event-description-date">@item.Date</span>
<div class="timeline-dot" style="@(!string.IsNullOrEmpty(item.Color) ? $"background-color:{item.Color};" : "")"></div>
<span class="event-description">@item.Title</span>
</li>
}
</ul>

<p class="more-info">@MoreInfo</p>
<p class="updated-at">@UpdatedAtMessage</p>
</div>
24 changes: 24 additions & 0 deletions src/Blashing.Widgets/Timeline/TimelineWidget.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Blashing.Core.Components;
using Microsoft.AspNetCore.Components;

namespace Blashing.Widgets.Timeline;

public partial class TimelineWidget : BaseWidget
{
[Parameter]
public string? Title { get; set; }

[Parameter]
public string? MoreInfo { get; set; }

[Parameter]
public string? UpdatedAtMessage { get; set; }

[Parameter]
public List<TimelineItem>? Items { get; set; } = new();

protected override void OnParametersSet()
{
BackgroundColor ??= "#4b4b4b";
}
}
75 changes: 75 additions & 0 deletions src/Blashing.Widgets/Timeline/TimelineWidget.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.widget-timeline {
background-color: #4b4b4b;
padding-bottom: 70px;
overflow-y: auto;
}

.widget-timeline .title {
color: rgba(255, 255, 255, 0.7);
}

.widget-timeline .more-info {
color: rgba(255, 255, 255, 0.7);
}

.widget-timeline .updated-at {
color: rgba(0, 0, 0, 0.3);
}

.widget-timeline .timeline-list {
list-style: none;
padding: 10px 20px;
margin: 0;
position: relative;
}

/* vertical line through the centre of the dots */
.widget-timeline .timeline-list::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 2px;
transform: translateX(-50%);
background: #E8F770;
}

.widget-timeline .timeline-event {
display: flex;
align-items: center;
margin-bottom: 18px;
position: relative;
}

/* left column: date */
.widget-timeline .timeline-event .event-description-date {
flex: 1;
text-align: right;
padding-right: 14px;
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
white-space: nowrap;
}

/* centre: dot */
.widget-timeline .timeline-event .timeline-dot {
flex-shrink: 0;
width: 14px;
height: 14px;
border-radius: 50%;
background: #E8F770;
z-index: 1;
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.5);
}

/* right column: title */
.widget-timeline .timeline-event .event-description {
flex: 1;
padding-left: 14px;
color: white;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
90 changes: 90 additions & 0 deletions src/Blashing.Widgets/Timeline/TimelineWidget.razor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #4b4b4b;
$timeline-color: #E8F770;
$dot-color: #E8F770;

$title-color: rgba(255, 255, 255, 0.7);
$moreinfo-color: rgba(255, 255, 255, 0.7);

// ----------------------------------------------------------------------------
// Widget-timeline styles
// ----------------------------------------------------------------------------
.widget-timeline {

background-color: $background-color;
padding-bottom: 70px;
overflow-y: auto;

.title {
color: $title-color;
}

.more-info {
color: $moreinfo-color;
}

.updated-at {
color: rgba(0, 0, 0, 0.3);
}

.timeline-list {
list-style: none;
padding: 10px 20px;
margin: 0;
position: relative;

// vertical line through the centre of the dots
&::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 2px;
transform: translateX(-50%);
background: $timeline-color;
}
}

.timeline-event {
display: flex;
align-items: center;
margin-bottom: 18px;
position: relative;

// left column: date
.event-description-date {
flex: 1;
text-align: right;
padding-right: 14px;
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
white-space: nowrap;
}

// centre: dot
.timeline-dot {
flex-shrink: 0;
width: 14px;
height: 14px;
border-radius: 50%;
background: $dot-color;
z-index: 1;
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.5);
}

// right column: title
.event-description {
flex: 1;
padding-left: 14px;
color: white;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}

}
26 changes: 26 additions & 0 deletions src/Blashing.Widgets/Timeline/_/timeline.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Dashing.Timeline extends Dashing.Widget

ready: ->
@renderTimeline(@get('events'))

onData: (data) ->
# Handle incoming data
# You can access the html node of this widget with `@node` E8F770 616161
# Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in.
if data.events
@renderTimeline(data.events)

renderTimeline: (events) ->
# Margins: zero if not set or the same as the opposite margin
# (you likely want this to keep the chart centered within the widget)
left = @get('leftMargin') || 0
right = @get('rightMargin') || left
top = @get('topMargin') || 0
bottom = @get('bottomMargin') || top

container = $(@node).parent()
# Gross hacks. Let's fix this.
width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1) - left - right
height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) - ($(@node).find("h1").outerHeight() + 12) - top - bottom
id = "." + @get('id')
TimeKnots.draw(id, events, {horizontalLayout: false, color: "#222222", height: height, width: width, showLabels: true, labelFormat:"%H:%M"});
3 changes: 3 additions & 0 deletions src/Blashing.Widgets/Timeline/_/timeline.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<h1 data-bind="title"></h1>

<div class="timeline"></div>
Loading