-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathApp.razor
More file actions
170 lines (155 loc) · 6.06 KB
/
App.razor
File metadata and controls
170 lines (155 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
@using NCrontab
@using Microsoft.Extensions.Logging
@using Microsoft.AspNetCore.WebUtilities
@inject ILogger<Index> Logger
@inject NavigationManager NavigationManager
@inject IJSRuntime JSRuntime
<label class="section-label" for="crontab-expression">NCrontab Expression</label>
<div class="input-group mb-3">
<input @oninput="OnInput"
value="@cronExpression"
placeholder="e.g. 0 0 12 * */2 Mon"
class="form-control font-monospace"
id="crontab-expression" />
<button
@onclick="PrintOccurrences"
type="button"
class="btn btn-primary"
disabled="@(!isExpressionValid)">
Print More
</button>
<button @onclick="CopyLink" type="button" class="btn @(copied ? "btn-success" : "btn-outline-brand")">@(copied ? "Copied!" : "Copy Link")</button>
</div>
<div class="d-flex align-items-center mb-2">
<span class="section-label mb-0">Occurrences</span>
@if (occurrences.Count > 0)
{
<span class="badge bg-secondary ms-2">@occurrences.Count</span>
}
</div>
@if (!string.IsNullOrEmpty(errorMessage))
{
<div class="alert alert-danger" role="alert">
@errorMessage
</div>
}
@if (!isExpression6PartFormat)
{
<div class="alert alert-warning" role="alert">
You are using the classic five-part CRON format. Services such as
Azure Functions and Azure WebJobs require the six-part format.
</div>
}
<ul class="occurrences-list">
@foreach (var occurence in occurrences)
{
var (relClass, relText) = GetRelativeTime(occurence);
<li>
<span>@occurence.ToString("yyyy-MM-dd HH:mm:ss")</span>
<span class="occurrence-relative @relClass">@relText</span>
</li>
}
</ul>
@code {
private const string DefaultCronExpression = "0 0 12 * */2 Mon";
private DateTime startDate = DateTime.Today;
private string cronExpression = DefaultCronExpression;
private CrontabSchedule cronSchedule = CrontabSchedule.Parse(DefaultCronExpression, new CrontabSchedule.ParseOptions { IncludingSeconds = true });
private List<DateTime> occurrences = new List<DateTime>();
private string errorMessage = string.Empty;
private bool isExpression6PartFormat = true;
private bool isExpressionValid = true;
private int? pendingScrollToIndex;
private bool copied;
protected override void OnInitialized()
{
var uri = new Uri(NavigationManager.Uri);
var queryPairs = QueryHelpers.ParseQuery(uri.Query);
if (queryPairs.ContainsKey("expression"))
{
cronExpression = ReadableQueryToCron(queryPairs["expression"].First()!);
}
SetExpression();
}
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (pendingScrollToIndex.HasValue)
{
var index = pendingScrollToIndex.Value;
pendingScrollToIndex = null;
((IJSInProcessRuntime)JSRuntime).InvokeVoid("scrollToOccurrence", index);
}
return Task.CompletedTask;
}
private void PrintOccurrences()
{
AppendOccurrences();
pendingScrollToIndex = occurrences.Count - 1;
}
private void AppendOccurrences()
{
var mostRecentDate = occurrences.Count == 0 ? startDate : occurrences.Last();
for (int i = 0; i < 10; i++)
{
mostRecentDate = cronSchedule.GetNextOccurrence(mostRecentDate);
occurrences.Add(mostRecentDate);
}
}
private void OnInput(ChangeEventArgs e)
{
cronExpression = e.Value?.ToString() ?? string.Empty;
SetExpression();
}
private void SetExpression()
{
occurrences.Clear();
isExpression6PartFormat = cronExpression
.Trim()
.Replace(@"\t", " ")
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Count() == 6;
try
{
cronSchedule = CrontabSchedule.Parse(cronExpression, new CrontabSchedule.ParseOptions { IncludingSeconds = isExpression6PartFormat });
errorMessage = string.Empty;
isExpressionValid = true;
AppendOccurrences();
}
catch (CrontabException exception)
{
Logger.LogError(exception, exception.Message);
errorMessage = exception.Message;
isExpression6PartFormat = true;
isExpressionValid = false;
}
}
private async Task CopyLink()
{
var uri = new Uri(NavigationManager.Uri);
var readableCron = CronToReadableQuery(cronExpression);
var uriToCopy = $"{uri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped)}?expression={readableCron}";
await JSRuntime.InvokeVoidAsync("copyText", uriToCopy);
copied = true;
await InvokeAsync(StateHasChanged);
await Task.Delay(2000);
copied = false;
}
private static (string cssClass, string text) GetRelativeTime(DateTime dateTime)
{
var diff = dateTime - DateTime.Now;
var abs = diff.Duration();
bool future = diff > TimeSpan.Zero;
string cssClass = diff <= TimeSpan.Zero ? "occurrence-past"
: diff.TotalDays <= 7 ? "occurrence-soon" : "occurrence-future";
(int value, string unit) = abs.TotalSeconds < 60 ? ((int)abs.TotalSeconds, "second")
: abs.TotalMinutes < 60 ? ((int)abs.TotalMinutes, "minute")
: abs.TotalHours < 24 ? ((int)abs.TotalHours, "hour")
: abs.TotalDays < 30 ? ((int)abs.TotalDays, "day")
: abs.TotalDays < 365 ? ((int)(abs.TotalDays / 30), "month")
: ((int)(abs.TotalDays / 365), "year");
string label = value == 1 ? unit : unit + "s";
return (cssClass, future ? $"in {value} {label}" : $"{value} {label} ago");
}
private string CronToReadableQuery(string query) => query.Replace(' ', '+');
private string ReadableQueryToCron(string query) => query.Replace('+', ' ');
}