Skip to content
21 changes: 17 additions & 4 deletions src/platforms/Riverside.Elapsed.App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Uno.Resizetizer;
using Riverside.Elapsed;
using Riverside.Elapsed.App.ViewModels;
using Riverside.Elapsed.App.Extensions;
using Riverside.Elapsed.App.ViewModels.Timelapses.Drafts;

namespace Riverside.Elapsed.App;

Expand Down Expand Up @@ -85,8 +87,13 @@
{
// TODO: Register your services
//services.AddSingleton<IMyService, MyService>();
services.AddDrafts();
})
.UseNavigation(RegisterRoutes)
.UseSerialization(serialization =>
{
serialization.AddSingleton(Constants.SerializerOptions);
})
);
MainWindow = builder.Window;

Expand Down Expand Up @@ -117,16 +124,22 @@
new ViewMap(ViewModel: typeof(ShellViewModel)),
new ViewMap<LoginPage, LoginViewModel>(),
new ViewMap<MainPage, MainViewModel>(),
new DataViewMap<SecondPage, SecondViewModel, Entity>()
new DataViewMap<SecondPage, SecondViewModel, Entity>(),

new ViewMap<DraftsPage, DraftsViewModel>(),
new DataViewMap<DraftDetailsPage, DraftDetailsViewModel, DraftListItem>(),

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Debug, App, net10.0-desktop)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Release, App, net10.0-ios)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Release, App, net10.0-browserwasm)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Release, App, net10.0-windows10.0.26100.0)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Debug, App, net10.0-android)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Debug, App, net10.0-browserwasm)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Release, App, net10.0-desktop)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Release, App, net10.0-android)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Debug, App, net10.0-windows10.0.26100.0)

Invalid expression term ')'

Check failure on line 130 in src/platforms/Riverside.Elapsed.App/App.xaml.cs

View workflow job for this annotation

GitHub Actions / app (Debug, App, net10.0-ios)

Invalid expression term ')'
);

routes.Register(
new RouteMap("", View: views.FindByViewModel<ShellViewModel>(),
Nested:
[
new ("Login", View: views.FindByViewModel<LoginViewModel>()),
new ("Main", View: views.FindByViewModel<MainViewModel>(), IsDefault:true),
new ("Second", View: views.FindByViewModel<SecondViewModel>()),
new("Login", View: views.FindByViewModel<LoginViewModel>()),
new("Main", View: views.FindByViewModel<MainViewModel>(), IsDefault:true),
new("Second", View: views.FindByViewModel<SecondViewModel>()),

new("Drafts", View: views.FindByViewModel<DraftsViewModel>()),
new("Draft", View: views.FindByViewModel<DraftDetailsViewModel>()),
]
)
);
Expand Down
32 changes: 32 additions & 0 deletions src/platforms/Riverside.Elapsed.App/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Riverside.Elapsed.App.Converters.Json;

namespace Riverside.Elapsed.App;

public static class Constants
{
public static readonly JsonSerializerOptions SerializerOptions = GetJsonSerializerOptions();

public static JsonSerializerOptions GetJsonSerializerOptions()
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
IgnoreReadOnlyProperties = true,
NumberHandling = JsonNumberHandling.Strict,
//ReferenceHandler = ReferenceHandler.IgnoreCycles,
};

options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
//options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true));
//options.Converters.Add(new TimeSpanSecondsJsonConverter());
//options.Converters.Add(new UriJsonConverter());

return options;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Riverside.Elapsed.App.Converters.Json;

public sealed class TimeSpanSecondsJsonConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Number && reader.TryGetDouble(out var seconds))
return TimeSpan.FromSeconds(seconds);

if (reader.TokenType == JsonTokenType.String)
{
var s = reader.GetString();
if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var sec))
return TimeSpan.FromMilliseconds(sec);
}

throw new JsonException("Invalid timespan value.");
}

public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.TotalSeconds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Riverside.Elapsed.App.Converters.Json;

public sealed class UriJsonConverter : JsonConverter<Uri>
{
public override Uri? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var s = reader.GetString();
if (string.IsNullOrWhiteSpace(s))
return new Uri("about:blank");

return new Uri(s, UriKind.Absolute);
}

public override void Write(Utf8JsonWriter writer, Uri value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
12 changes: 12 additions & 0 deletions src/platforms/Riverside.Elapsed.App/Models/Admin/AdminListPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json;

namespace Riverside.Elapsed.App.Models.Admin;

public class AdminListPage
{
public EntityType Entity;
public IReadOnlyList<JsonElement> Rows;
public long Total;
public long Page;
public long PageSize;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Riverside.Elapsed.App.Models.Admin;

public class AdminSearchResult
{
public EntityType Entity;
public string Id;
public string DisplayText;
}
12 changes: 12 additions & 0 deletions src/platforms/Riverside.Elapsed.App/Models/Admin/AdminStats.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Riverside.Elapsed.App.Models.Admin;

public class AdminStats
{
public double TotalLoggedSeconds;
public long TotalProjects;
public long TotalUsers;
}
10 changes: 10 additions & 0 deletions src/platforms/Riverside.Elapsed.App/Models/Admin/EntityType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Riverside.Elapsed.App.Models.Admin;

public enum EntityType
{
User,
Timelapse,
Comment,
DraftTimelapse,
LegacyTimelapse,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Riverside.Elapsed.App.Models.Developer;

public class DeveloperApp
{
public Guid AppId;
public string Name;
public string Description;
public Uri HomepageUrl;
public Uri? IconUrl;
public IReadOnlyList<Uri> RedirectUris;
public IReadOnlyList<string> Scopes;
public TrustLevel TrustLevel;
public string ClientId;
public DateTimeOffset CreatedAt;
public User.User? CreatedBy;
}
15 changes: 15 additions & 0 deletions src/platforms/Riverside.Elapsed.App/Models/Developer/OAuthGrant.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Riverside.Elapsed.App.Models.Developer;

public class OAuthGrant
{
public string GrantId;
public string ServiceClientId;
public string ServiceName;
public IReadOnlyList<string> Scopes;
public DateTimeOffset CreatedAt;
public DateTimeOffset? LastUsedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Riverside.Elapsed.App.Models.Developer;

public enum TrustLevel
{
Untrusted,
Trusted,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Riverside.Elapsed.App.Models.Timelapses.Local;

namespace Riverside.Elapsed.App.Models.Primitives;

/// <summary>
/// Represents a locally-stored file that can be uploaded to the Lapse server online.
/// </summary>
public interface IUploadable
{
string FilePath { get; init; }
long FileSizeBytes { get; init; }

string? UploadToken { get; init; }
TusUploadState Upload { get; init; }
}
13 changes: 13 additions & 0 deletions src/platforms/Riverside.Elapsed.App/Models/Timelapses/Comment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Riverside.Elapsed.App.Models.Timelapses;

public class Comment
{
public string CommentId;
public string Content;
public User.User Author;
public DateTimeOffset CreatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Riverside.Elapsed.App.Models.Timelapses;

public class CursorPage<T> // infinite scroll
{
public IReadOnlyList<T> Items;
public string? NextCursor;
}
12 changes: 12 additions & 0 deletions src/platforms/Riverside.Elapsed.App/Models/Timelapses/DraftEdit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Riverside.Elapsed.App.Models.Timelapses;

public class DraftEdit
{
public double BeginSeconds;
public double EndSeconds;
public EditKind Kind;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Riverside.Elapsed.App.Models.Timelapses;

public class DraftTimelapse
{
public string DraftTimelapseId;
public string Name;
public string Description;
public DateTimeOffset CreatedAt;
public User.User Owner;
public Guid DeviceId;
public string IvHex;
public Uri PreviewThumbnailUrl;
public IReadOnlyList<Uri> Sessions;
public IReadOnlyList<DraftEdit> EditList;
public string? AssociatedTimelapseId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Riverside.Elapsed.App.Models.Timelapses;

public enum EditKind
{
Cut,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Riverside.Elapsed.App.Models.Timelapses.Local;

public class DraftPipelineState
{
public enum Phase
{
LocalOnly,
CreatingRemoteDraft,
Encrypting,
Uploading,
ReadyToPublish,
Publishing,
Published,
Error,
}

public Phase CurrentPhase { get; init; } = Phase.LocalOnly;
public double Progress { get; init; }
public string? LastError { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Riverside.Elapsed.App.Models.Timelapses.Local;

public sealed record LocalDraft
{
public Guid LocalDraftId { get; init; }
public string Name { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset LastModifiedAt { get; init; }

public Guid DeviceId { get; init; }

public IReadOnlyList<long> Snapshots { get; init; } = []; // milliseconds since epoch
public List<DraftEdit> EditList { get; init; } = [];

public List<LocalSession> Sessions { get; init; } = [];
public LocalThumbnail Thumbnail { get; init; } = new();

public RemoteDraftSync? Remote { get; init; }
public DraftPipelineState State { get; init; } = new();

public static LocalDraft Create(Guid deviceId, DateTimeOffset now)
{
return new LocalDraft
{
LocalDraftId = Guid.NewGuid(),
DeviceId = deviceId,
CreatedAt = now,
Name = string.Empty,
Description = string.Empty,
Snapshots = [],
EditList = [],
Sessions = [],
Thumbnail = new LocalThumbnail(),
State = new DraftPipelineState(),
Remote = null,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Riverside.Elapsed.App.Models.Timelapses.Local;

public sealed record LocalDraftIndex
{
public IReadOnlyList<LocalDraftIndexItem> Drafts { get; init; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Riverside.Elapsed.App.Models.Timelapses.Local;

public sealed record LocalDraftIndexItem
{
public Guid LocalDraftId { get; init; }
public string Name { get; init; } = string.Empty;
public DateTimeOffset LastModifiedAt { get; init; }

public bool HasRemoteDraft { get; init; }
public string? RemoteDraftTimelapseId { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Riverside.Elapsed.App.Models.Primitives;

namespace Riverside.Elapsed.App.Models.Timelapses.Local;

public sealed record LocalSession : IUploadable
{
public Guid LocalSessionId { get; init; }

public string FilePath { get; init; } = string.Empty;
public long FileSizeBytes { get; init; }

public string? UploadToken { get; init; }
public TusUploadState Upload { get; init; } = new();
}
Loading
Loading