diff --git a/.gitignore b/.gitignore index 7086f64..8423c65 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ release/ **/coveragereport/**/** src/**/coverage.json +nohup.out diff --git a/src/Blashing.Shared/Pages/AdditionalWidgets.razor b/src/Blashing.Shared/Pages/AdditionalWidgets.razor index 519e1ed..7b0c583 100644 --- a/src/Blashing.Shared/Pages/AdditionalWidgets.razor +++ b/src/Blashing.Shared/Pages/AdditionalWidgets.razor @@ -16,6 +16,25 @@ +
+
+ +

Timeline

+ + + +
+
+
@@ -65,5 +84,18 @@ BackgroundColor="#8fb347"> + + + + \ No newline at end of file diff --git a/src/Blashing.Shared/_Imports.razor b/src/Blashing.Shared/_Imports.razor index 584fc30..8b155df 100644 --- a/src/Blashing.Shared/_Imports.razor +++ b/src/Blashing.Shared/_Imports.razor @@ -19,3 +19,4 @@ @using Blashing.Widgets; @using Blashing.Widgets.ServerStatusSquares; @using Blashing.Widgets.CircleCIBuildStatus; +@using Blashing.Widgets.Timeline; diff --git a/src/Blashing.Stories/Stories/TimelineWidget.stories.razor b/src/Blashing.Stories/Stories/TimelineWidget.stories.razor new file mode 100644 index 0000000..b8a0049 --- /dev/null +++ b/src/Blashing.Stories/Stories/TimelineWidget.stories.razor @@ -0,0 +1,27 @@ +@using Blashing.Widgets.Timeline + +@attribute [Stories("Example/Timeline")] + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Blashing.Widgets.Tests/TimelineWidgetTest.cs b/src/Blashing.Widgets.Tests/TimelineWidgetTest.cs new file mode 100644 index 0000000..2fdc973 --- /dev/null +++ b/src/Blashing.Widgets.Tests/TimelineWidgetTest.cs @@ -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() + { + new TimelineItem("10:00", "Event One"), + new TimelineItem("11:30", "Event Two") + }; + + var cut = Render(parameters => parameters + .Add(p => p.Title, title) + .Add(p => p.MoreInfo, moreInfo) + .Add(p => p.UpdatedAtMessage, updatedAtMessage) + .Add(p => p.Items, items) + ); + + var expectedTitleMarkup = $"

{title}

"; + cut.FindAll("h1")[0].MarkupMatches(expectedTitleMarkup); + + var expectedMoreInfoMarkup = $"

{moreInfo}

"; + cut.FindAll("p")[0].MarkupMatches(expectedMoreInfoMarkup); + + var expectedUpdatedAtMessageMarkup = $"

{updatedAtMessage}

"; + 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() + { + new TimelineItem("10:00", "Event One"), + new TimelineItem("11:30", "Event Two") + }; + + var cut = Render(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); + } +} diff --git a/src/Blashing.Widgets/Timeline/TimelineItem.cs b/src/Blashing.Widgets/Timeline/TimelineItem.cs new file mode 100644 index 0000000..e1207c7 --- /dev/null +++ b/src/Blashing.Widgets/Timeline/TimelineItem.cs @@ -0,0 +1,3 @@ +namespace Blashing.Widgets.Timeline; + +public record TimelineItem(string Date, string Title, string? Color = null); diff --git a/src/Blashing.Widgets/Timeline/TimelineWidget.razor b/src/Blashing.Widgets/Timeline/TimelineWidget.razor new file mode 100644 index 0000000..606f7bb --- /dev/null +++ b/src/Blashing.Widgets/Timeline/TimelineWidget.razor @@ -0,0 +1,19 @@ +@inherits Blashing.Core.Components.BaseWidget; + +
+

@Title

+ +
    + @foreach (var item in Items ?? Enumerable.Empty()) + { +
  • + @item.Date +
    + @item.Title +
  • + } +
+ +

@MoreInfo

+

@UpdatedAtMessage

+
diff --git a/src/Blashing.Widgets/Timeline/TimelineWidget.razor.cs b/src/Blashing.Widgets/Timeline/TimelineWidget.razor.cs new file mode 100644 index 0000000..898e381 --- /dev/null +++ b/src/Blashing.Widgets/Timeline/TimelineWidget.razor.cs @@ -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? Items { get; set; } = new(); + + protected override void OnParametersSet() + { + BackgroundColor ??= "#4b4b4b"; + } +} diff --git a/src/Blashing.Widgets/Timeline/TimelineWidget.razor.css b/src/Blashing.Widgets/Timeline/TimelineWidget.razor.css new file mode 100644 index 0000000..747b028 --- /dev/null +++ b/src/Blashing.Widgets/Timeline/TimelineWidget.razor.css @@ -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; +} diff --git a/src/Blashing.Widgets/Timeline/TimelineWidget.razor.scss b/src/Blashing.Widgets/Timeline/TimelineWidget.razor.scss new file mode 100644 index 0000000..6f84833 --- /dev/null +++ b/src/Blashing.Widgets/Timeline/TimelineWidget.razor.scss @@ -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; + } + } + +} diff --git a/src/Blashing.Widgets/Timeline/_/timeline.coffee b/src/Blashing.Widgets/Timeline/_/timeline.coffee new file mode 100644 index 0000000..9b2af42 --- /dev/null +++ b/src/Blashing.Widgets/Timeline/_/timeline.coffee @@ -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"}); diff --git a/src/Blashing.Widgets/Timeline/_/timeline.html b/src/Blashing.Widgets/Timeline/_/timeline.html new file mode 100644 index 0000000..4852ca5 --- /dev/null +++ b/src/Blashing.Widgets/Timeline/_/timeline.html @@ -0,0 +1,3 @@ +

+ +
diff --git a/src/Blashing.Widgets/Timeline/_/timeline.scss b/src/Blashing.Widgets/Timeline/_/timeline.scss new file mode 100644 index 0000000..669c4ba --- /dev/null +++ b/src/Blashing.Widgets/Timeline/_/timeline.scss @@ -0,0 +1,34 @@ +// ---------------------------------------------------------------------------- +// Sass declarations +// ---------------------------------------------------------------------------- +$background-color: #4b4b4b; + +$title-color: rgba(255, 255, 255, 0.7); +$moreinfo-color: rgba(255, 255, 255, 0.7); + +// ---------------------------------------------------------------------------- +// Widget-text styles +// ---------------------------------------------------------------------------- +.widget-timeline { + + background-color: $background-color; + padding-bottom: 70px; + + .title { + color: $title-color; + } + + .more-info { + color: $moreinfo-color; + } + + .event-description { + fill: white; + font-size: 14px; + } + + .event-description-date { + fill: white; + font-size: 12px; + } +}