Skip to content
This repository has been archived by the owner on Feb 13, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4715 from toggl-open-source/feature/timeline_ui/g…
Browse files Browse the repository at this point in the history
…ap_running_te

Gap button to create TE starting from last TE until current time
  • Loading branch information
skel35 authored Dec 2, 2020
2 parents 619086c + 694dbef commit 0d667da
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,28 @@
RadiusX="15"
RadiusY="7">
</Rectangle>
<Viewbox Stretch="None">
<Path Opacity="0.348" Fill="{DynamicResource Toggl.DarkGrayBrush}" Data="M 0 5h4.25v4.25h1.5v-4.25h4.25v-1.5h-4.25v-4.25h-1.5v4.25h-4.25Z"/>
</Viewbox>
<ContentControl Content="{StaticResource Toggl.Timeline.AddNewEntryIcon}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button" x:Key="Toggl.Timeline.GapButton.Running">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid UseLayoutRounding="False" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Path SnapsToDevicePixels="True" Stroke="{DynamicResource Toggl.LightGrayBrush}" StrokeThickness="2" StrokeDashArray="0.94 0.94" Grid.Row="0"
Data="M 1,10 A 9,8 0 1 1 19,10">
</Path>
<Path SnapsToDevicePixels="True" Stretch="Fill" Stroke="{DynamicResource Toggl.LightGrayBrush}" StrokeThickness="2" StrokeDashArray="1 1" Grid.Row="1"
Data="M 0,0 v20 M 20,0 v20">
</Path>
<ContentControl Content="{StaticResource Toggl.Timeline.AddNewEntryIcon}" Grid.Row="0" Grid.RowSpan="2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,7 @@
<Setter Property="Fill" Value="{DynamicResource Toggl.SecondaryTextBrush}"/>
<Setter Property="Data" Value="M12.042 4.5c.487 0 .882.316.882.706v2.017c1.513.27 2.647 1.242 2.647 2.697a.7.7 0 0 1-.706.693.7.7 0 0 1-.706-.693c0-.815-.873-1.386-2.117-1.386-1.245 0-2.118.571-2.118 1.386 0 .686.488.965 2.289 1.407 2.434.598 3.358 1.127 3.358 2.753 0 1.455-1.134 2.427-2.647 2.697v2.017c0 .39-.395.706-.882.706-.488 0-.883-.316-.883-.706v-2.017c-1.513-.27-2.647-1.242-2.647-2.697a.7.7 0 0 1 .706-.693.7.7 0 0 1 .706.693c0 .815.873 1.386 2.118 1.386 1.244 0 2.117-.571 2.117-1.386 0-.686-.488-.965-2.289-1.407-2.434-.598-3.358-1.127-3.358-2.753 0-1.455 1.134-2.427 2.647-2.697V5.206c0-.39.395-.706.883-.706z"/>
</Style>
<Viewbox Stretch="None" x:Key="Toggl.Timeline.AddNewEntryIcon" x:Shared="False">
<Path Opacity="0.348" Fill="{DynamicResource Toggl.DarkGrayBrush}" Data="M 0 5h4.25v4.25h1.5v-4.25h4.25v-1.5h-4.25v-4.25h-1.5v4.25h-4.25Z"/>
</Viewbox>
</ResourceDictionary>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public static class TimelineConstants
public const double TimeEntryBlockWidth = 20;
public const double GapBetweenOverlappingTEs = 5;
public const double AcceptableBlocksOverlap = 1e-5;
public const double MinGapTimeEntryHeight = 10;

public static IReadOnlyDictionary<int, int> ScaleModes { get; } = new Dictionary<int, int>()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,19 @@ public GapTimeEntryBlock(Func<double, double, string> addNewTimeEntry)

public class TimeEntryBlock : TimelineBlockViewModel
{
[Reactive]
public string Color { get; set; }
[Reactive]
public bool ShowDescription { get; set; }
[Reactive]
public string Description { get; set; }
[Reactive]
public string ProjectName { get; set; }
[Reactive]
public string ClientName { get; set; }
public string TaskName { get; set; }
[Reactive]
public bool HasTag { get; set; }
[Reactive]
public bool IsBillable { get; set; }
public string Color => _timeEntry.Color;
public string Description => _timeEntry.Description.IsNullOrEmpty() ? "No Description" : _timeEntry.Description;
public string ProjectName => _timeEntry.ProjectLabel;
public string ClientName => _timeEntry.ClientLabel;
public string TaskName => _timeEntry.TaskLabel;
public bool HasTag => !_timeEntry.Tags.IsNullOrEmpty();
public bool IsBillable => _timeEntry.Billable;
public string Duration { [ObservableAsProperty]get; }
public string StartEndCaption { [ObservableAsProperty]get; }
public ReactiveCommand<Unit, Unit> OpenEditView { get; }
public string TimeEntryId { get; }
public string TimeEntryId => _timeEntry.GUID;

[Reactive]
public bool IsEditViewOpened { get; set; }
Expand All @@ -64,15 +58,19 @@ public class TimeEntryBlock : TimelineBlockViewModel
[Reactive]
public bool IsDragged { get; set; }

public DateTime DateCreated { get; }
public ulong Started => _timeEntry.Started;
public ulong Ended => _timeEntry.Ended;

private DateTime DateCreated { get; }

private readonly double _hourHeight;
private readonly Toggl.TogglTimeEntryView _timeEntry;

public TimeEntryBlock(string timeEntryId, int hourHeight, DateTime date)
public TimeEntryBlock(Toggl.TogglTimeEntryView te, int hourHeight, DateTime date)
{
_hourHeight = hourHeight;
DateCreated = date;
TimeEntryId = timeEntryId;
_timeEntry = te;
OpenEditView = ReactiveCommand.Create(() => Toggl.Edit(TimeEntryId, false, Toggl.Description));
var startEndObservable = this.WhenAnyValue(x => x.VerticalOffset, x => x.Height, (offset, height) =>
(Started: TimelineUtils.ConvertOffsetToDateTime(offset, date, _hourHeight), Ended: TimelineUtils.ConvertOffsetToDateTime(offset + height, date, _hourHeight)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,12 @@ public TimelineViewModel()
tuple.TimeEntries.Where(b => b.Key != tuple.Running?.GUID)
.ToDictionary(pair => pair.Key, pair => pair.Value))
.ToPropertyEx(this, x => x.TimeEntryBlocks);
blocksObservable.Select(blocks => GenerateGapTimeEntryBlocks(blocks.Values.ToList(), SelectedScaleMode, SelectedDate))
blocksObservable.Select(blocks => GenerateGapTimeEntryBlocks(blocks.Values.ToList()))
.ToPropertyEx(this, x => x.GapTimeEntryBlocks);
blocksWithRunningObservable
.Select(tuple =>
GenerateRunningGapBlock(tuple.TimeEntries.Values, tuple.Running, CurrentTimeOffset, SelectedDate))
.ToPropertyEx(this, x => x.RunningGapTimeEntryBlock);

this.WhenAnyValue(x => x.TimeEntryBlocks)
.Where(blocks => blocks != null && blocks.Any())
Expand All @@ -88,10 +92,10 @@ public TimelineViewModel()
Observable.Timer(TimeSpan.Zero,TimeSpan.FromMinutes(1))
.Select(_ => ConvertTimeIntervalToHeight(DateTime.Today, DateTime.Now, SelectedScaleMode))
.Subscribe(h => CurrentTimeOffset = h);
this.WhenAnyValue(x => x.CurrentTimeOffset).Where(_ => RunningTimeEntryBlock != null)
.Select(off => Math.Max(TimelineConstants.MinTimeEntryBlockHeight,
CurrentTimeOffset - RunningTimeEntryBlock.VerticalOffset))
.Subscribe(h => RunningTimeEntryBlock.Height = h);
this.WhenAnyValue(x => x.CurrentTimeOffset).Select(offset => (Offset: offset,
Block: RunningTimeEntryBlock as TimelineBlockViewModel ?? RunningGapTimeEntryBlock))
.Where(tuple => tuple.Block != null)
.Subscribe(tuple => tuple.Block.Height = tuple.Offset - tuple.Block.VerticalOffset);
this.WhenAnyValue(x => x.TimeEntryBlocks, x => x.RunningTimeEntryBlock, x => x.IsTodaySelected,
(blocks, running, isToday) => blocks?.Any() == true || (running != null && isToday))
.ToPropertyEx(this, x => x.AnyTimeEntries);
Expand Down Expand Up @@ -205,18 +209,11 @@ private static Dictionary<string, TimeEntryBlock> ConvertTimeEntriesToBlocks(Lis
? TimelineUtils.ConvertOffsetToUnixTime(currentTimeOffset, selectedDate, TimelineConstants.ScaleModes[selectedScaleMode])
: entry.Ended;
var height = ConvertTimeIntervalToHeight(startTime, Toggl.DateTimeFromUnix(ended), selectedScaleMode);
var block = new TimeEntryBlock(entry.GUID, TimelineConstants.ScaleModes[selectedScaleMode], selectedDate)
var block = new TimeEntryBlock(entry, TimelineConstants.ScaleModes[selectedScaleMode], selectedDate)
{
Height = height,
VerticalOffset = ConvertTimeIntervalToHeight(selectedDate, startTime, selectedScaleMode),
Color = entry.Color,
Description = entry.Description.IsNullOrEmpty() ? "No Description" : entry.Description,
ProjectName = entry.ProjectLabel,
ClientName = entry.ClientLabel,
TaskName = entry.TaskLabel,
ShowDescription = true,
HasTag = !entry.Tags.IsNullOrEmpty(),
IsBillable = entry.Billable
ShowDescription = true
};
if (entry.Started < ended)
{
Expand All @@ -239,7 +236,7 @@ private static Dictionary<string, TimeEntryBlock> ConvertTimeEntriesToBlocks(Lis
var time1 = te1.Type == TimeStampType.End ? te1.Block.Bottom : te1.Block.VerticalOffset;
var time2 = te2.Type == TimeStampType.End ? te2.Block.Bottom : te2.Block.VerticalOffset;
var res = time1 - time2;
if (Math.Abs(res) < TimelineConstants.AcceptableBlocksOverlap)
if (res.IsNearEqual(0, TimelineConstants.AcceptableBlocksOverlap))
{
var getPriority = new Func<TimeStampType, int>(t =>
t == TimeStampType.End ? 0 : t == TimeStampType.Empty ? 1 : 2);
Expand Down Expand Up @@ -288,7 +285,7 @@ public static double ConvertTimeIntervalToHeight(DateTime start, DateTime end, i
return timeInterval * TimelineConstants.ScaleModes[scaleMode] / 60;
}

private static List<GapTimeEntryBlock> GenerateGapTimeEntryBlocks(List<TimeEntryBlock> timeEntries, int selectedScaleMode, DateTime selectedDate)
private static List<GapTimeEntryBlock> GenerateGapTimeEntryBlocks(List<TimeEntryBlock> timeEntries)
{
var gaps = new List<GapTimeEntryBlock>();
timeEntries.Sort((te1,te2) => te1.VerticalOffset.CompareTo(te2.VerticalOffset));
Expand All @@ -297,23 +294,47 @@ private static List<GapTimeEntryBlock> GenerateGapTimeEntryBlocks(List<TimeEntry
{
if (lastTimeEntry != null && entry.VerticalOffset > lastTimeEntry.Bottom)
{
var block = new GapTimeEntryBlock((offset, height) => AddNewTimeEntry(offset, height, selectedScaleMode, selectedDate))
var gapStart = lastTimeEntry.Ended + 1;
var block = new GapTimeEntryBlock((offset, height) => Toggl.CreateEmptyTimeEntry(gapStart, entry.Started))
{
Height = entry.VerticalOffset - lastTimeEntry.Bottom,
VerticalOffset = lastTimeEntry.Bottom,
HorizontalOffset = 0
};
if (block.Height > 10) // Don't display to small gaps not to obstruct the view
if (block.Height >= TimelineConstants.MinGapTimeEntryHeight) // Don't display too small gaps not to obstruct the view
gaps.Add(block);
}
lastTimeEntry = lastTimeEntry == null || entry.Bottom > lastTimeEntry.Bottom
? entry
: lastTimeEntry;
}

return gaps;
}

private static GapTimeEntryBlock GenerateRunningGapBlock(IEnumerable<TimeEntryBlock> timeEntries, Toggl.TogglTimeEntryView? running,
double curTimeOffset, DateTime selectedDate)
{
if (running != null || selectedDate.Date != DateTime.Today) return null;

var lastTimeEntry = timeEntries.Aggregate(default(TimeEntryBlock),
(max, next) => max == null || next.Ended > max.Ended ? next : max);
if (lastTimeEntry != null && curTimeOffset >= lastTimeEntry.Bottom + TimelineConstants.MinGapTimeEntryHeight)
return new GapTimeEntryBlock(
(offset, height) =>
{
var id = Toggl.Start("", "", 0, 0, "", "");
Toggl.SetTimeEntryStartTimeStamp(id, (long)lastTimeEntry.Ended+1);
return id;
})
{
Height = curTimeOffset - lastTimeEntry.Bottom,
VerticalOffset = lastTimeEntry.Bottom,
HorizontalOffset = 0
};
return null;
}

public static string AddNewTimeEntry(double offset, double height, int scaleMode, DateTime date)
{
var started = TimelineUtils.ConvertOffsetToUnixTime(offset, date,
Expand Down Expand Up @@ -355,6 +376,8 @@ public static string AddNewTimeEntry(double offset, double height, int scaleMode

public List<GapTimeEntryBlock> GapTimeEntryBlocks { [ObservableAsProperty] get; }

public GapTimeEntryBlock RunningGapTimeEntryBlock { [ObservableAsProperty] get; }

[Reactive]
public string SelectedForEditTEId { get; set; }
public ReactiveCommand<Unit, int> IncreaseScale { get; }
Expand Down
14 changes: 12 additions & 2 deletions src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,11 @@
MouseDown="OnTimeEntryCanvasMouseDown"
MouseMove="OnTimeEntryCanvasMouseMove"
MouseUp="OnTimeEntryCanvasMouseUp"/>
<Canvas Name="RunningTimeEntryCanvas" Grid.Row="1" Grid.Column="2" Margin="10 0 0 0" Visibility="{Binding RunningTimeEntryBlock, Converter={StaticResource NullToCollapsedConverter}}">
<Canvas Name="RunningTimeEntryCanvas" Grid.Row="1" Grid.Column="2" Margin="10 0 0 0">
<togglDesktop:TimelineRunningTimeEntryBlock DataContext="{Binding RunningTimeEntryBlock}"
Canvas.Top="{Binding VerticalOffset}"
Canvas.Left="{Binding HorizontalOffset}">
Canvas.Left="{Binding HorizontalOffset}"
Visibility="{Binding Path=., Converter={StaticResource NullToCollapsedConverter}}">
<togglDesktop:TimelineRunningTimeEntryBlock.Style>
<Style TargetType="FrameworkElement">
<Style.Triggers>
Expand All @@ -165,6 +166,15 @@
</Style>
</togglDesktop:TimelineRunningTimeEntryBlock.Style>
</togglDesktop:TimelineRunningTimeEntryBlock>
<Button DataContext="{Binding RunningGapTimeEntryBlock}"
Style="{StaticResource Toggl.Timeline.GapButton.Running}"
Canvas.Top="{Binding VerticalOffset}"
Canvas.Left="{Binding HorizontalOffset}"
Height="{Binding Height}"
Width="{x:Static res:TimelineConstants.TimeEntryBlockWidth}"
Command="{Binding CreateTimeEntryFromBlock}"
Visibility="{Binding Path=., Converter={StaticResource NullToCollapsedConverter}}">
</Button>
</Canvas>
<Canvas Grid.Row="1" Grid.ColumnSpan="5" Grid.Column="0"
Visibility="{Binding IsTodaySelected, Converter={StaticResource BooleanToVisibilityConverter}}"
Expand Down
11 changes: 10 additions & 1 deletion src/ui/windows/TogglDesktop/TogglDesktop/utilities/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,14 @@ public static bool IsWindows7() =>
Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1;

#endregion environment
}

#region working with numbers

public static bool IsNearEqual(this double first, double second, double precision = 1e-5)
{
return Math.Abs(first - second) <= precision;
}

#endregion
}
}

0 comments on commit 0d667da

Please sign in to comment.