Vendor globe.gl and its Earth textures
globe.gl 2.46.2 (MIT, three.js r185 bundled) and the NASA Blue Marble and Black Marble textures from three-globe 2.45.3, copied once from the npm registry; SOURCES.md records versions, licences and sha256, and the bundled libraries' notices are in LICENSES-bundled.txt. The service worker leaves the folder out of the offline precache: the feed loads it the first time it shows the globe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
b9040bbd80
commit
f41ebf2160
26 files changed
+122
-1101
No files matched your search
@@ -1,106 +0,0 @@
|
||||
@inherits LocalizableComponentBase
|
||||
|
||||
<section class="flex flex-col gap-4">
|
||||
|
||||
@if (isPosting)
|
||||
{
|
||||
<div class="block p-3 md:p-4 rounded-xl neo-raised neo-xs">
|
||||
<MessageUpsertForm OnMessageSubmit="OnMessageSubmit" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex justify-between gap-3">
|
||||
|
||||
<EditForm Model="Filters">
|
||||
<div class="flex flex-row gap-3">
|
||||
@if (OnTimelineChanged.HasDelegate)
|
||||
{
|
||||
<DSelect TValue="TimelineType" class="w-auto" Value="Filters.TimelineType"
|
||||
ValueChanged="async v => await OnTimelineChange(v)" ValueExpression="() => Filters.TimelineType">
|
||||
<Prefix>
|
||||
<i class="@Filters.TimelineType.GetTimelineTypeIcon()"></i>
|
||||
</Prefix>
|
||||
<ChildContent>
|
||||
@foreach (var timelineType in Enum.GetValues<TimelineType>())
|
||||
{
|
||||
<option value="@timelineType">@Localizer[timelineType.ToString()]</option>
|
||||
}
|
||||
</ChildContent>
|
||||
</DSelect>
|
||||
}
|
||||
@if (OnTimeSortingChanged.HasDelegate)
|
||||
{
|
||||
<DSelect TValue="TimeSortingType" class="w-auto" Value="Filters.TimeSortingType"
|
||||
ValueChanged="async v => await OnTimeSortChange(v)" ValueExpression="() => Filters.TimeSortingType">
|
||||
<Prefix>
|
||||
<i class="ion-md-funnel"></i>
|
||||
</Prefix>
|
||||
<ChildContent>
|
||||
@foreach (var timeSortingType in Enum.GetValues<TimeSortingType>())
|
||||
{
|
||||
<option value="@timeSortingType">@Localizer[timeSortingType.ToString()]</option>
|
||||
}
|
||||
</ChildContent>
|
||||
</DSelect>
|
||||
}
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@if (OnMessageSubmit.HasDelegate)
|
||||
{
|
||||
<div>
|
||||
<DButton Active="isPosting" OnClick="OpenCloseMessageForm">
|
||||
<i class="ion-md-create"></i>
|
||||
<span>@Localizer["Post"]</span>
|
||||
</DButton>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div></div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<MessageForm> OnMessageSubmit { get; set; }
|
||||
[Parameter] public EventCallback<TimelineType> OnTimelineChanged { get; set; }
|
||||
[Parameter] public EventCallback<TimeSortingType> OnTimeSortingChanged { get; set; }
|
||||
[Inject] ILocalStorageService LocalStorage { get; set; }
|
||||
bool isPosting;
|
||||
ActionBarFilter Filters { get; set; } = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var filters = await LocalStorage.GetItemAsync<ActionBarFilter>(nameof(ActionBarFilter));
|
||||
IsLoading = false;
|
||||
if (filters is not null)
|
||||
{
|
||||
Filters = filters;
|
||||
return;
|
||||
}
|
||||
|
||||
await LocalStorage.SetItemAsync(nameof(ActionBarFilter), Filters);
|
||||
}
|
||||
|
||||
void OpenCloseMessageForm()
|
||||
{
|
||||
isPosting = !isPosting;
|
||||
}
|
||||
|
||||
async Task OnTimelineChange(TimelineType timelineType)
|
||||
{
|
||||
Filters.TimelineType = timelineType;
|
||||
await LocalStorage.SetItemAsync(nameof(ActionBarFilter), Filters);
|
||||
await OnTimelineChanged.InvokeAsync(Filters.TimelineType);
|
||||
}
|
||||
|
||||
async Task OnTimeSortChange(TimeSortingType timeSorting)
|
||||
{
|
||||
Filters.TimeSortingType = timeSorting;
|
||||
await LocalStorage.SetItemAsync(nameof(ActionBarFilter), Filters);
|
||||
await OnTimeSortingChanged.InvokeAsync(Filters.TimeSortingType);
|
||||
}
|
||||
}
|
||||
@@ -1,352 +0,0 @@
|
||||
@inherits LocalizableComponentBase
|
||||
|
||||
<EditForm Model="MessageForm" OnValidSubmit="OnValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
|
||||
<div class="flex flex-col mb-3">
|
||||
<DInput TValue="string" class="w-full rounded-t-[1.4rem] rounded-b-lg" maxlength="64" autocomplete="off"
|
||||
placeholder="@Localizer["Title (optional)"]" Value="@MessageForm.Title"
|
||||
ValueChanged="(v) => OnTitleChanged(v)"
|
||||
ValueExpression="() => MessageForm.Title" />
|
||||
<div class="relative mt-1">
|
||||
<DTextarea TValue="string" @bind-Value="MessageForm.Content" Immediate="true"
|
||||
class="w-full rounded-b-[1.4rem] rounded-t-lg" maxlength="@MaxContentLength" autocomplete="off"
|
||||
placeholder="@Localizer["oh shit... here we go again"]" rows="3" />
|
||||
<span class="absolute text-xs opacity-50 right-2 bottom-1">@ContentLength/@MaxContentLength</span>
|
||||
</div>
|
||||
@if (showPreviewButton && isPreviewOpen && MessageForm.Content is { Length: > 0 })
|
||||
{
|
||||
<div class="relative mt-1 px-8">
|
||||
<div class="neo-inset neo-xs rounded-t-lg rounded-b-[1.4rem] px-2 py-1 md:px-3 md:py-2 text-xs md:text-sm">
|
||||
@switch (MessageForm.ContentType)
|
||||
{
|
||||
case ContentType.Markdown:
|
||||
@((MarkupString)Markdown.ToHtml(MessageForm.Content))
|
||||
break;
|
||||
case ContentType.HTML:
|
||||
<p>@((MarkupString)MessageForm.Content)</p>
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 mb-3 @SUtility.IfTrueThen(MessageForm.Media.Count == 0, "hidden")">
|
||||
@foreach (var media in MessageForm.Media)
|
||||
{
|
||||
switch (MessageForm.MediaType)
|
||||
{
|
||||
case MediaType.Images:
|
||||
<div class="flex w-full items-center gap-3 rounded-xl p-3 md:p-4 neo-raised neo-2xs">
|
||||
<img alt="@media.AltText" class="object-cover rounded-lg neo-raised neo-2xs max-h-24 md:max-h-40 max-w-[6rem] md:max-w-[12rem]" src="@media.Base64Preview" />
|
||||
<div class="flex w-full self-start flex-col justify-between gap-2">
|
||||
<div class="flex w-full gap-3">
|
||||
<div class="flex-1">
|
||||
<p class="text-xs md:text-sm break-all">
|
||||
<i class="ion-md-image"></i> <b>@media.FileName</b>
|
||||
</p>
|
||||
<p class="text-xs break-all">
|
||||
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(Localizer)
|
||||
</p>
|
||||
</div>
|
||||
<DButton Size="DSize.Sm" class="self-start" OnClick="() => RemoveFile(media)">
|
||||
<i class="ion-md-trash text-base text-red-400"></i>
|
||||
</DButton>
|
||||
</div>
|
||||
<DTextarea TValue="string" @bind-Value="media.AltText" Size="DSize.Sm" class="w-full rounded-lg!" autocomplete="off"
|
||||
placeholder="@Localizer["Alternative text"]" rows="1" />
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
case MediaType.Video:
|
||||
case MediaType.Documents:
|
||||
<div class="flex items-center gap-3 rounded-xl p-3 md:p-4 neo-raised neo-2xs">
|
||||
<span>
|
||||
<i class="text-2xl @MessageForm.MediaType.GetMediaTypeIcon()"></i>
|
||||
</span>
|
||||
<div class="flex flex-col w-full gap-1">
|
||||
<p class="text-xs md:text-sm break-all">
|
||||
<b>@media.FileName</b>
|
||||
</p>
|
||||
<p class="text-xs break-all">
|
||||
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(Localizer)
|
||||
</p>
|
||||
</div>
|
||||
<DButton Size="DSize.Sm" OnClick="() => RemoveFile(media)">
|
||||
<i class="ion-md-trash text-base text-red-400"></i>
|
||||
</DButton>
|
||||
</div>
|
||||
break;
|
||||
case MediaType.Audio:
|
||||
<div class="flex items-center gap-3 rounded-xl p-3 md:p-4 neo-raised neo-2xs">
|
||||
<span>
|
||||
<i class="text-2xl @MessageForm.MediaType.GetMediaTypeIcon()"></i>
|
||||
</span>
|
||||
<div class="flex flex-col w-full gap-1">
|
||||
<p class="text-xs md:text-sm break-all">
|
||||
<b>@media.FileName</b>
|
||||
</p>
|
||||
<p class="text-xs break-all">
|
||||
<i class="ion-md-code"></i> @media.ContentType <i class="ion-md-fitness"></i> @media.Size.GetFileSize(Localizer)
|
||||
</p>
|
||||
<audio controls="controls" class="w-full max-h-8">
|
||||
<source src="@media.Base64Preview" type="@media.ContentType">
|
||||
</audio>
|
||||
</div>
|
||||
<DButton Size="DSize.Sm" OnClick="() => RemoveFile(media)">
|
||||
<i class="ion-md-trash text-base text-red-400"></i>
|
||||
</DButton>
|
||||
</div>
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<ErrorViewer ContainerClass="mb-3" AdditionalError="@contentError" AdditionalErrors="fileErrors" />
|
||||
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div class="flex flex-wrap items-start gap-3">
|
||||
<DDropdown @bind-IsOpen="MessageForm.IsScopeOptionsOpen"
|
||||
TriggerClass="@(MessageForm.IsScopeOptionsOpen ? "btn btn-sm btn-active" : "btn btn-sm")"
|
||||
ContentClass="mt-2 flex gap-2 p-2 rounded-box"
|
||||
title="@Localizer["Message scope type: {0}", Localizer[MessageForm.MessageType.ToString()]]">
|
||||
<Trigger>
|
||||
<i class="@MessageForm.MessageType.GetMessageTypeIcon() text-base"></i>
|
||||
</Trigger>
|
||||
<ChildContent>
|
||||
<DButton Size="DSize.Sm" Active="MessageForm.MessageType is MessageType.Direct" OnClick="() => UpdateMessageType(MessageType.Direct)"
|
||||
title="@Localizer[MessageType.Direct.ToString()]">
|
||||
<i class="ion-md-paper-plane text-base"></i>
|
||||
</DButton>
|
||||
<DButton Size="DSize.Sm" Active="MessageForm.MessageType is MessageType.FollowersOnly" OnClick="() => UpdateMessageType(MessageType.FollowersOnly)"
|
||||
title="@Localizer[MessageType.FollowersOnly.ToString()]">
|
||||
<i class="ion-md-lock text-base"></i>
|
||||
</DButton>
|
||||
<DButton Size="DSize.Sm" Active="MessageForm.MessageType is MessageType.Unlisted" OnClick="() => UpdateMessageType(MessageType.Unlisted)"
|
||||
title="@Localizer[MessageType.Unlisted.ToString()]">
|
||||
<i class="ion-md-unlock text-base"></i>
|
||||
</DButton>
|
||||
<DButton Size="DSize.Sm" Active="MessageForm.MessageType is MessageType.Public" OnClick="() => UpdateMessageType(MessageType.Public)"
|
||||
title="@Localizer[MessageType.Public.ToString()]">
|
||||
<i class="ion-md-globe text-base"></i>
|
||||
</DButton>
|
||||
</ChildContent>
|
||||
</DDropdown>
|
||||
|
||||
@* A label styled as a button opens the hidden file input. *@
|
||||
<label class="btn btn-sm @SUtility.IfTrueThen(ShouldDisableUpload(), "btn-disabled")">
|
||||
<InputFile accept="@acceptedFilesTypes" class="hidden" multiple="@ShouldHaveMultipleUpload()" OnChange="OnFileChange" disabled="@ShouldDisableUpload()" />
|
||||
<i class="ion-md-attach text-base"></i>
|
||||
@if (MessageForm.Media.Count != 0)
|
||||
{
|
||||
<span>+@MessageForm.Media.Count</span>
|
||||
}
|
||||
</label>
|
||||
|
||||
<DSelect TValue="MediaType" Size="DSize.Sm" class="w-auto" Value="MessageForm.MediaType" ValueChanged="v => OnMediaTypeChanged(v)"
|
||||
ValueExpression="() => MessageForm.MediaType" disabled="@(MessageForm.Media.Count > 0)">
|
||||
<Prefix>
|
||||
<i class="@MessageForm.MediaType.GetMediaTypeIcon()"></i>
|
||||
</Prefix>
|
||||
<ChildContent>
|
||||
@foreach (var mediaType in Enum.GetValues<MediaType>())
|
||||
{
|
||||
<option value="@mediaType">@Localizer[mediaType.ToString()]</option>
|
||||
}
|
||||
</ChildContent>
|
||||
</DSelect>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<DSelect TValue="ContentType" Size="DSize.Sm" class="w-auto" Value="MessageForm.ContentType" ValueChanged="v => OnContentTypeChanged(v)"
|
||||
ValueExpression="() => MessageForm.ContentType">
|
||||
<Prefix>
|
||||
<i class="@MessageForm.ContentType.GetContentTypeIcon()"></i>
|
||||
</Prefix>
|
||||
<ChildContent>
|
||||
@foreach (var contentType in Enum.GetValues<ContentType>())
|
||||
{
|
||||
<option value="@contentType">@Localizer[contentType.ToString()]</option>
|
||||
}
|
||||
</ChildContent>
|
||||
</DSelect>
|
||||
</div>
|
||||
|
||||
@if (showPreviewButton)
|
||||
{
|
||||
<DButton Size="DSize.Sm" Active="isPreviewOpen" OnClick="OnOpenClosePreview" title="@Localizer["Show preview"]">
|
||||
<i class="@SUtility.IfTrueThen(isPreviewOpen, "ion-md-eye-off", "ion-md-eye") text-base"></i>
|
||||
</DButton>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<DButton Size="DSize.Sm" Type="submit" Disabled="IsDefaultDisabled">
|
||||
<span>@Localizer["Post"]</span>
|
||||
<i class="ion-md-send"></i>
|
||||
</DButton>
|
||||
|
||||
</div>
|
||||
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
const int MaxContentLength = 5_000;
|
||||
|
||||
[Inject] ILoggingService Logger { get; set; }
|
||||
[Parameter] public Message AnsweringMessage { get; set; }
|
||||
[Parameter] public EventCallback<MessageForm> OnMessageSubmit { get; set; }
|
||||
MessageForm MessageForm { get; set; } = new();
|
||||
List<string> fileErrors = [];
|
||||
string contentError;
|
||||
string acceptedFilesTypes = ".jpg,.jpeg,.png,.gif";
|
||||
bool showPreviewButton;
|
||||
bool isPreviewOpen;
|
||||
|
||||
// Content is null until the user types: a form starts without text.
|
||||
int ContentLength => MessageForm.Content?.Length ?? 0;
|
||||
|
||||
int MaximumFileCount => MessageForm.MediaType switch
|
||||
{
|
||||
MediaType.Images => 5,
|
||||
MediaType.Video or MediaType.Audio => 1,
|
||||
MediaType.Documents => 3,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
long MaximumFileSize => MessageForm.MediaType switch
|
||||
{
|
||||
MediaType.Images or MediaType.Documents => 3_145_728,
|
||||
MediaType.Audio => 5_242_880,
|
||||
MediaType.Video => 20_971_520,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
IsLoading = false;
|
||||
if (AnsweringMessage is null)
|
||||
return;
|
||||
|
||||
MessageForm.Title = AnsweringMessage.Title;
|
||||
MessageForm.RootMessageId = AnsweringMessage.RootMessageId ?? AnsweringMessage.MessageId;
|
||||
}
|
||||
|
||||
void UpdateMessageType(MessageType messageType)
|
||||
{
|
||||
MessageForm.MessageType = messageType;
|
||||
MessageForm.IsScopeOptionsOpen = false;
|
||||
}
|
||||
|
||||
bool ShouldDisableUpload() => MessageForm.Media.Count >= MaximumFileCount;
|
||||
|
||||
bool ShouldHaveMultipleUpload() => MessageForm.MediaType is MediaType.Images or MediaType.Documents;
|
||||
|
||||
async Task OnFileChange(InputFileChangeEventArgs eventArgs)
|
||||
{
|
||||
fileErrors = [];
|
||||
var maximumFileCount = MaximumFileCount;
|
||||
if (eventArgs.FileCount + MessageForm.Media.Count > maximumFileCount)
|
||||
{
|
||||
fileErrors.Add(Localizer["The maximum number of files accepted is {0}, but {1} were supplied.", maximumFileCount,
|
||||
MessageForm.Media.Count + eventArgs.FileCount]);
|
||||
return;
|
||||
}
|
||||
|
||||
var maximumFileSize = MaximumFileSize;
|
||||
foreach (var file in eventArgs.GetMultipleFiles(maximumFileCount))
|
||||
{
|
||||
if (file.Name == default || file.ContentType == default || MessageForm.Media.Any(m => m.FileName == file.Name))
|
||||
continue;
|
||||
|
||||
if (file.Size > maximumFileSize)
|
||||
{
|
||||
fileErrors.Add(Localizer["Supplied file \"{0}\" with size {1} exceeds the maximum of {2}.", file.Name,
|
||||
file.Size.GetFileSize(Localizer), maximumFileSize.GetFileSize(Localizer)]);
|
||||
continue;
|
||||
}
|
||||
|
||||
var uploadMedia = await ReadFile(file, maximumFileSize);
|
||||
if (uploadMedia is null)
|
||||
{
|
||||
fileErrors.Add(Localizer["The file \"{0}\" could not be read.", file.Name]);
|
||||
continue;
|
||||
}
|
||||
|
||||
MessageForm.Media.Add(uploadMedia);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads a picked file into memory; null when the browser can't read it (logged).</summary>
|
||||
async Task<UploadMedia> ReadFile(IBrowserFile file, long maximumFileSize, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
await using var fileStream = file.OpenReadStream(maximumFileSize, cancellationToken);
|
||||
await fileStream.CopyToAsync(memoryStream, cancellationToken);
|
||||
var uploadMedia = new UploadMedia
|
||||
{
|
||||
FileName = file.Name,
|
||||
ContentType = file.ContentType,
|
||||
Size = file.Size,
|
||||
Blob = memoryStream.ToArray()
|
||||
};
|
||||
if (MessageForm.MediaType is MediaType.Images or MediaType.Audio)
|
||||
uploadMedia.Base64Preview = $"data:{uploadMedia.ContentType};base64,{Convert.ToBase64String(uploadMedia.Blob)}";
|
||||
return uploadMedia;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Logger.ProcessError(ex, $"{nameof(MessageUpsertForm)}.{nameof(ReadFile)}:{file.Name}", cancellationToken);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveFile(UploadMedia media)
|
||||
{
|
||||
MessageForm.Media.Remove(media);
|
||||
}
|
||||
|
||||
void OnTitleChanged(string value)
|
||||
{
|
||||
MessageForm.Title = value;
|
||||
}
|
||||
|
||||
void OnContentTypeChanged(ContentType contentType)
|
||||
{
|
||||
MessageForm.ContentType = contentType;
|
||||
showPreviewButton = contentType is ContentType.Markdown or ContentType.HTML;
|
||||
}
|
||||
|
||||
void OnMediaTypeChanged(MediaType mediaType)
|
||||
{
|
||||
MessageForm.MediaType = mediaType;
|
||||
acceptedFilesTypes = mediaType switch
|
||||
{
|
||||
MediaType.Images => ".jpg,.jpeg,.png,.gif",
|
||||
MediaType.Video => ".webm,.mp4,.m4v",
|
||||
MediaType.Audio => ".mp3,.wav,.flac,.m4a",
|
||||
MediaType.Documents => ".xlsx,.csv,.ppt,.odt",
|
||||
_ => default
|
||||
};
|
||||
}
|
||||
|
||||
void OnOpenClosePreview()
|
||||
{
|
||||
isPreviewOpen = !isPreviewOpen;
|
||||
}
|
||||
|
||||
async Task OnValidSubmit()
|
||||
{
|
||||
contentError = default;
|
||||
if (string.IsNullOrWhiteSpace(MessageForm.Content) && MessageForm.Media.Count == 0)
|
||||
{
|
||||
contentError = Localizer["Missing content, either message or media"];
|
||||
return;
|
||||
}
|
||||
|
||||
await OnMessageSubmit.InvokeAsync(MessageForm);
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
using decePubClient.Models;
|
||||
using decePubClient.Models.Types;
|
||||
|
||||
using Markdig;
|
||||
|
||||
namespace decePubClient.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Mock data until the app is wired to PrivaPub: the signed-in user, the feed shown while IndexedDB holds no messages,
|
||||
/// and the thread around an expanded message.
|
||||
/// </summary>
|
||||
public static class Faker
|
||||
{
|
||||
public static User CurrentUser => new()
|
||||
{
|
||||
Id = "45f14fa8-c40f-4121-997c-ef2542196a50",
|
||||
UserName = "@loosy@letsrulethe.world",
|
||||
DisplayName = "loosy",
|
||||
PictureUrl = "https://letsrulethe.world/media/ddd91d51-dc12-4b82-86a2-2a28cc959183/L51ZAL4QEL07",
|
||||
BackgroundUrl = "https://letsrulethe.world/media/9a3cc6c4-1018-4f41-86c7-f3e63e362bf4/EXM8RUK10B0Z",
|
||||
ProfileUrl = "https://letsrulethe.world/users/loosy"
|
||||
};
|
||||
|
||||
static IReadOnlyList<MessageUser> Users =>
|
||||
[
|
||||
CurrentUser.ToMessageUser(),
|
||||
new()
|
||||
{
|
||||
UserId = "7b5703dc-aee8-46b1-aed2-cd06021a1c0c",
|
||||
DisplayName = "loweel",
|
||||
UserName = "@loweel@bbs.keinpfusch.net",
|
||||
PictureUrl = "https://ipfs.keinpfusch.net/ipfs/bafybeiazrrmngqww7mfag76gmcmw6l2nohckuh6yvldbzatkls5yyznxb4?name=Yb7peXARjY3B_A.png",
|
||||
BackgroundUrl = "https://ipfs.keinpfusch.net/ipfs/bafkreialv6avp3xykmfc75daevjz4k3chd27eop7o62nznafy46pwjpd2y?name=QBZNoDonJ1HvEw.jpg",
|
||||
ProfileUrl = "https://letsrulethe.world/users/AG6rE2nRya826QEJFY"
|
||||
},
|
||||
new()
|
||||
{
|
||||
UserId = "bc9c2a2b-fc5f-42fc-b907-ac30203eed45",
|
||||
DisplayName = "Valentina Nappi",
|
||||
UserName = "@valentina.nappi@mastodon.uno",
|
||||
PictureUrl = "https://s-media-cache-ak0.pinimg.com/564x/a0/30/00/a030003d2e506ec9731b8460127d15f9.jpg",
|
||||
BackgroundUrl = "https://wallup.net/wp-content/uploads/2016/01/225560-nature-landscape-fall-path-forest-mist-morning-trees-leaves-sunlight.jpg",
|
||||
ProfileUrl = "https://mastodon.uno/web/@valenappi@beta.birdsite.live"
|
||||
}
|
||||
];
|
||||
|
||||
public static MessageUser GetRandomUser()
|
||||
{
|
||||
return Users[Random.Shared.Next(0, Users.Count)];
|
||||
}
|
||||
|
||||
public static BoostingUser GetRandomBoostingUser()
|
||||
{
|
||||
var user = Users[Random.Shared.Next(0, Users.Count)];
|
||||
return new()
|
||||
{
|
||||
UserId = user.UserId,
|
||||
DisplayName = user.DisplayName,
|
||||
UserName = user.UserName,
|
||||
PictureUrl = user.PictureUrl,
|
||||
ProfileUrl = user.ProfileUrl
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The seed feed. Two entries share a MessageId on purpose: the second is the first one boosted by someone, which
|
||||
/// is how the feed shows a boost (a copy of the message with BoostingUser set).
|
||||
/// </summary>
|
||||
public static List<Message> SeedMessages()
|
||||
{
|
||||
var messages = new List<Message>();
|
||||
messages.Add(new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "992167EE-8947-4823-B1E2-B4E1019B6974",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = default,
|
||||
Content = "test di messaggio normale senza titolo",
|
||||
Medias = [],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0, 100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
});
|
||||
messages.Add(new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "C5307644-4EA8-4B5F-A240-7CFE4DE34741",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = "sopra la panca la capra canta",
|
||||
Content = Markdown.ToHtml("test di messaggio con titolo"),
|
||||
Medias = [],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0, 100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
});
|
||||
messages.Add(new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "E473D562-D210-4453-93DA-157D4B90B0D8",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = default,
|
||||
Content = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd test di messaggio con overflow del testo",
|
||||
Medias = [],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0, 100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
});
|
||||
messages.Add(new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "adf89dc6-0484-430e-a7c9-fcb1179b0c28",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = default,
|
||||
Content = "test video",
|
||||
Medias =
|
||||
[
|
||||
new()
|
||||
{
|
||||
FileName = "8252d175e714db88beb8ee0349dbac90405d3d5148e96e0cfd7de3b0876dbb1b.mp4",
|
||||
Url = "https://ihatebeinga.live/media/8252d175e714db88beb8ee0349dbac90405d3d5148e96e0cfd7de3b0876dbb1b.mp4",
|
||||
AltText = "https://ihatebeinga.live/media/8252d175e714db88beb8ee0349dbac90405d3d5148e96e0cfd7de3b0876dbb1b.mp4",
|
||||
ContentType = "video/mp4"
|
||||
}
|
||||
],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0, 100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
});
|
||||
messages.Add(new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "96025b43-5235-44e9-a3fa-98df31edfbfb",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = default,
|
||||
Content = "test immagine",
|
||||
Medias =
|
||||
[
|
||||
new()
|
||||
{
|
||||
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
ContentType = "image/jpg"
|
||||
}
|
||||
],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0, 100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
});
|
||||
messages.Add(new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "277ca76b-aaad-4aa6-95fb-25f59e2fddf9",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = default,
|
||||
Content = "test audio",
|
||||
Medias =
|
||||
[
|
||||
new()
|
||||
{
|
||||
FileName = "09bfb06d0096975f.mp3",
|
||||
Url = "https://cdn.masto.host/mastodonuno/media_attachments/files/107/798/288/501/917/857/original/09bfb06d0096975f.mp3",
|
||||
AltText = "09bfb06d0096975f.mp3",
|
||||
ContentType = "audio/mp3"
|
||||
}
|
||||
],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0, 100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
});
|
||||
messages.Add(new()
|
||||
{
|
||||
BoostingUser = GetRandomBoostingUser(),
|
||||
RootMessageId = default,
|
||||
MessageId = "de64e520-a7d3-4f31-b5a9-ba236f3b146c",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = default,
|
||||
Content = "test immagini",
|
||||
Medias =
|
||||
[
|
||||
new()
|
||||
{
|
||||
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
ContentType = "image/jpg"
|
||||
},
|
||||
new()
|
||||
{
|
||||
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
ContentType = "image/jpg"
|
||||
},
|
||||
new()
|
||||
{
|
||||
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
ContentType = "image/jpg"
|
||||
}
|
||||
],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0,100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
});
|
||||
messages.Add(new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "de64e520-a7d3-4f31-b5a9-ba236f3b146c",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = default,
|
||||
Content = "test immagini",
|
||||
Medias =
|
||||
[
|
||||
new()
|
||||
{
|
||||
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
ContentType = "image/jpg"
|
||||
},
|
||||
new()
|
||||
{
|
||||
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
ContentType = "image/jpg"
|
||||
},
|
||||
new()
|
||||
{
|
||||
FileName = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
Url = "https://ihatebeinga.live/media/51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
AltText = "51a7cf620f1dd096ac5867d5b78f8c71d57a23972b31c64ec124a8c41e77a618.jpg",
|
||||
ContentType = "image/jpg"
|
||||
}
|
||||
],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0,100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
});
|
||||
return messages;
|
||||
}
|
||||
|
||||
/// <summary>The messages before and after an expanded one.</summary>
|
||||
public static (List<Message> Ancestors, List<Message> Descendants) Thread(Message message) =>
|
||||
(
|
||||
[
|
||||
new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "51C698E3-7C28-4C90-9212-48D5C81DE089",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = message.Title,
|
||||
Content = "<p>esempio di messaggio <b>precedente</b> a quello espanso</p>",
|
||||
Medias = [],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0, 100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
}
|
||||
],
|
||||
[
|
||||
new()
|
||||
{
|
||||
RootMessageId = default,
|
||||
MessageId = "0569A1DF-46FC-4485-9F11-F8CDFB794404",
|
||||
User = GetRandomUser(),
|
||||
MessageType = (MessageType)Random.Shared.Next(0, 4),
|
||||
Title = message.Title,
|
||||
Content = "<p>esempio di messaggio <b>successivo</b> a quello espanso</p>",
|
||||
Medias = [],
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(Random.Shared.Next(-5000, 0)),
|
||||
IsFavourite = Random.Shared.Next() % 2 == 0,
|
||||
BoostsCounter = Random.Shared.Next(0, 100),
|
||||
IsBoostedByCurrentUser = Random.Shared.Next() % 2 == 0,
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using decePubClient.Models.Types;
|
||||
|
||||
namespace decePubClient.Models;
|
||||
|
||||
public class ActionBarFilter
|
||||
{
|
||||
public TimelineType TimelineType { get; set; } = TimelineType.Home;
|
||||
public TimeSortingType TimeSortingType { get; set; } = TimeSortingType.Ascending;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace decePubClient.Models
|
||||
{
|
||||
public class BoostingUser
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string PictureUrl { get; set; }
|
||||
public string ProfileUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace decePubClient.Models
|
||||
{
|
||||
public interface CustomIPrincipal : IPrincipal
|
||||
{
|
||||
string UserId { get; set; }
|
||||
string UserName { get; set; }
|
||||
string DisplayName { get; set; }
|
||||
string PictureUrl { get; set; }
|
||||
string ProfileUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace decePubClient.Models
|
||||
{
|
||||
public class CustomPrincipal : CustomIPrincipal
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string PictureUrl { get; set; }
|
||||
public string ProfileUrl { get; set; }
|
||||
public bool IsAdmin { get; set; } = false;
|
||||
public IIdentity Identity { get; }
|
||||
|
||||
public CustomPrincipal(string userName, string authType) =>
|
||||
Identity = new GenericIdentity(userName, authType);
|
||||
|
||||
public bool IsInRole(string role) => false;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace decePubClient.Models
|
||||
{
|
||||
public class Media
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
public string AltText { get; set; }
|
||||
public byte[] Blob { get; set; } = []; //TODO TEMPORARY
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace decePubClient.Models
|
||||
{
|
||||
public class Mention
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string ProfileUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using decePubClient.Models.Types;
|
||||
|
||||
namespace decePubClient.Models
|
||||
{
|
||||
public class Message
|
||||
{
|
||||
public MessageUser User { get; set; } = new();
|
||||
public MessageType MessageType { get; set; } = MessageType.Public;
|
||||
public string RootMessageId { get; set; }
|
||||
public string MessageId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
public bool IsFavourite { get; set; } = false;
|
||||
public int BoostsCounter { get; set; } = 0;
|
||||
public bool IsBoostedByCurrentUser { get; set; } = false;
|
||||
public BoostingUser BoostingUser { get; set; }
|
||||
public List<Media> Medias { get; set; } = [];
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[Bindable(false), JsonIgnore]
|
||||
public bool IsOptionsOpen { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using decePubClient.Models.Types;
|
||||
using decePubClient.Resources;
|
||||
|
||||
namespace decePubClient.Models;
|
||||
|
||||
public class MessageForm
|
||||
{
|
||||
public string RootMessageId { get; set; }
|
||||
|
||||
[Required(ErrorMessageResourceName = ValidationNames.Required, ErrorMessageResourceType = typeof(ErrorMessages))]
|
||||
public MessageType MessageType { get; set; } = MessageType.Public;
|
||||
[Required(ErrorMessageResourceName = ValidationNames.Required, ErrorMessageResourceType = typeof(ErrorMessages))]
|
||||
public ContentType ContentType { get; set; } = ContentType.PlainText;
|
||||
[StringLength(64, ErrorMessageResourceName = ValidationNames.MaxLength, ErrorMessageResourceType = typeof(ErrorMessages))]
|
||||
public string Title { get; set; }
|
||||
[StringLength(5_000, ErrorMessageResourceName = ValidationNames.MaxLength, ErrorMessageResourceType = typeof(ErrorMessages))]
|
||||
public string Content { get; set; }
|
||||
public List<UploadMedia> Media { get; set; } = [];
|
||||
|
||||
[JsonIgnore, Bindable(false)]
|
||||
public MediaType MediaType { get; set; } = MediaType.Images;
|
||||
[JsonIgnore, Bindable(false)]
|
||||
public bool IsScopeOptionsOpen { get; set; } = false;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace decePubClient.Models
|
||||
{
|
||||
public class MessageUser
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string PictureUrl { get; set; }
|
||||
public string BackgroundUrl { get; set; }
|
||||
public string ProfileUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace decePubClient.Models.Types;
|
||||
|
||||
public enum ContentType
|
||||
{
|
||||
PlainText = 1,
|
||||
HTML,
|
||||
Markdown
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace decePubClient.Models.Types;
|
||||
|
||||
public enum TimeSortingType
|
||||
{
|
||||
Ascending,
|
||||
Descending
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
namespace decePubClient.Models
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public bool IsAuthenticated { get; set; } = false;
|
||||
public List<UserClaim> Claims { get; set; } = [];
|
||||
|
||||
public string UserName { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string PictureUrl { get; set; }
|
||||
public string BackgroundUrl { get; set; }
|
||||
public string ProfileUrl { get; set; }
|
||||
|
||||
public MessageUser ToMessageUser() => new()
|
||||
{
|
||||
UserId = Id,
|
||||
UserName = UserName,
|
||||
DisplayName = DisplayName,
|
||||
PictureUrl = PictureUrl,
|
||||
BackgroundUrl = BackgroundUrl,
|
||||
ProfileUrl = ProfileUrl
|
||||
};
|
||||
|
||||
public BoostingUser ToBoostingUser() => new()
|
||||
{
|
||||
UserId = Id,
|
||||
UserName = UserName,
|
||||
DisplayName = DisplayName,
|
||||
PictureUrl = PictureUrl,
|
||||
ProfileUrl = ProfileUrl
|
||||
};
|
||||
}
|
||||
|
||||
public class UserClaim
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
@page "/authentication/{action}"
|
||||
@inherits PagesBase
|
||||
|
||||
|
||||
<section class="block relative w-full h-full neo-inset neo-xs rounded-xl">
|
||||
|
||||
<div class="flex flex-col gap-4 p-4 md:p-5 w-full h-full absolute overflow-y-auto">
|
||||
|
||||
<RemoteAuthenticatorView Action="@Action" />
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string Action { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace decePubClient.Services
|
||||
{
|
||||
public class ApiAuthorizationOptionsConfiguration : IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>
|
||||
{
|
||||
public void Configure(RemoteAuthenticationOptions<ApiAuthorizationProviderOptions> options)
|
||||
{
|
||||
options.UserOptions.RoleClaim ??= "role";
|
||||
}
|
||||
|
||||
public void PostConfigure(string name, RemoteAuthenticationOptions<ApiAuthorizationProviderOptions> options)
|
||||
{
|
||||
if (string.Equals(name, Options.DefaultName))
|
||||
Configure(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
using collAnon.Client.Services;
|
||||
|
||||
using decePubClient.Extensions;
|
||||
using decePubClient.Models;
|
||||
using decePubClient.Models.Types;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
|
||||
namespace decePubClient.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Message actions. Still a mock: the app isn't wired to PrivaPub yet, so each method builds its result in memory.
|
||||
/// The signatures are the real ones (a Task<WebResult> whose Data is the updated entity), so wiring means
|
||||
/// calling httpService against PrivaPub's /clientapi/post/* routes inside the same try/catch, reading failures with
|
||||
/// ReadWebResult and the body with DefaultReadFromJsonAsync, as b2b.next's StoreService does.
|
||||
/// </summary>
|
||||
public class MessagesService(ILoggingService logger, CoalescingStringLocalizer localizer)
|
||||
{
|
||||
/// <summary>Posts a message or, with RootMessageId set, a reply; Data is the new Message.</summary>
|
||||
public Task<WebResult> SubmitMessage(MessageForm messageForm, User author, CancellationToken cancellationToken = default) =>
|
||||
Run(nameof(SubmitMessage), () => (object)new Message
|
||||
{
|
||||
RootMessageId = messageForm.RootMessageId,
|
||||
MessageId = Guid.NewGuid().ToString(),
|
||||
Content = messageForm.Content?.ParseContent(messageForm.ContentType),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
MessageType = messageForm.MessageType,
|
||||
Medias = [.. messageForm.Media.Select(m => new Media
|
||||
{
|
||||
FileName = m.FileName,
|
||||
AltText = m.AltText,
|
||||
ContentType = m.ContentType,
|
||||
Url = m.Base64Preview,
|
||||
Blob = m.Blob
|
||||
})],
|
||||
Title = messageForm.Title,
|
||||
User = author.ToMessageUser()
|
||||
}, cancellationToken);
|
||||
|
||||
/// <summary>Opens a reply of the given scope to a message; Data is the message.</summary>
|
||||
public Task<WebResult> ReplyMessage(Message messageToReply, MessageType messageType = MessageType.Public, CancellationToken cancellationToken = default) =>
|
||||
Run(nameof(ReplyMessage), () => messageToReply, cancellationToken);
|
||||
|
||||
/// <summary>Boosts the message, or takes the boost back; Data is the message with its counter updated.</summary>
|
||||
public Task<WebResult> BoostUnboostMessage(Message message, CancellationToken cancellationToken = default) =>
|
||||
Run(nameof(BoostUnboostMessage), () =>
|
||||
{
|
||||
message.BoostsCounter += message.IsBoostedByCurrentUser ? -1 : 1;
|
||||
message.IsBoostedByCurrentUser = !message.IsBoostedByCurrentUser;
|
||||
return message;
|
||||
}, cancellationToken);
|
||||
|
||||
/// <summary>Adds the message to the favourites, or removes it; Data is the updated message.</summary>
|
||||
public Task<WebResult> FavouriteUnfavouriteMessage(Message message, CancellationToken cancellationToken = default) =>
|
||||
Run(nameof(FavouriteUnfavouriteMessage), () =>
|
||||
{
|
||||
message.IsFavourite = !message.IsFavourite;
|
||||
return message;
|
||||
}, cancellationToken);
|
||||
|
||||
public Task<WebResult> DeleteMessage(Message message, CancellationToken cancellationToken = default) =>
|
||||
Run(nameof(DeleteMessage), () => message, cancellationToken);
|
||||
|
||||
public Task<WebResult> BlockUserFromMessage(MessageUser messageUser, CancellationToken cancellationToken = default) =>
|
||||
Run(nameof(BlockUserFromMessage), () => messageUser, cancellationToken);
|
||||
|
||||
public Task<WebResult> SilenceUserFromMessage(MessageUser messageUser, CancellationToken cancellationToken = default) =>
|
||||
Run(nameof(SilenceUserFromMessage), () => messageUser, cancellationToken);
|
||||
|
||||
/// <summary>The mock's stand-in for an API call: the same success/failure handling, with the outcome built locally.</summary>
|
||||
async Task<WebResult> Run(string caller, Func<object> action, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await Task.Yield();
|
||||
result.Data = action();
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.ProcessError(ex, $"{nameof(MessagesService)}.{caller}", cancellationToken);
|
||||
return result.Invalidate(localizer["The action could not be completed."], exception: ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
@inherits LocalizableComponentBase
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<DButton OnClick="BeginSignOut">
|
||||
@Localizer["Logout"]
|
||||
</DButton>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
<NavLink ActiveClass="btn-active" class="btn" href="authentication/login">
|
||||
@Localizer["Login"]
|
||||
</NavLink>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@code {
|
||||
private void BeginSignOut(MouseEventArgs args)
|
||||
{
|
||||
Navigation.NavigateTo("logout");
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
|
||||
const cacheNamePrefix = 'offline-cache-';
|
||||
const cacheName = `${cacheNamePrefix}0.0.1`;
|
||||
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
|
||||
const offlineAssetsExclude = [ /^service-worker\.js$/ ];
|
||||
// The globe (4 MB of script and textures) is fetched when the feed first shows it, never precached.
|
||||
const offlineAssetsExclude = [ /^service-worker\.js$/, /^vendor\/globe\// ];
|
||||
|
||||
async function onInstall(event) {
|
||||
console.info('Service worker: Install');
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Vasco Asturiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
Licences of the libraries bundled in globe.gl.min.js (globe.gl's own is LICENSE-globe.gl; three-globe, kapsule and
|
||||
accessor-fn carry the same MIT notice by the same author).
|
||||
|
||||
=== three.js r185 ===
|
||||
The MIT License
|
||||
|
||||
Copyright © 2010-2026 three.js authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
=== @tweenjs/tween.js ===
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2012 Tween.js authors.
|
||||
|
||||
Easing equations Copyright (c) 2001 Robert Penner http://robertpenner.com/easing/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
=== d3 modules (ISC) ===
|
||||
Copyright 2010-2023 Mike Bostock
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,
|
||||
provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
# The globe's vendored files
|
||||
|
||||
Copied once from the npm registry (registry.npmjs.org) on 2026-10-04; the build and the app never fetch them from
|
||||
anywhere else. To update, download the new tarballs from the registry, copy the same files, and replace the hashes.
|
||||
|
||||
| File | From | Licence | sha256 |
|
||||
|---|---|---|---|
|
||||
| `globe.gl.min.js` | `globe.gl` 2.46.2, `dist/globe.gl.min.js` (UMD, defines `window.Globe`) | MIT, `LICENSE-globe.gl` | `2c3e445c04d121215910a89688b96091c8a72071c122a4f830081a39b636c94c` |
|
||||
| `earth-blue-marble.jpg` | `three-globe` 2.45.3, `example/img/earth-blue-marble.jpg`, 4096×2048 | NASA Visible Earth "Blue Marble", public domain | `228deba2e4b600146bdcb6cfa359b8ead6aacc2b1c13550a29cd82824cfa1c01` |
|
||||
| `earth-night.jpg` | `three-globe` 2.45.3, `example/img/earth-night.jpg`, 4096×2048 | NASA Earth Observatory "Black Marble" (city lights), public domain | `355ab23dd1323315b393d7b91dd2d7ee223a1cbaaba2b48dc72ba90d371ced24` |
|
||||
|
||||
`globe.gl.min.js` bundles its dependencies: three.js r185 (MIT, © 2010–2026 three.js authors), three-globe (MIT, © 2019
|
||||
Vasco Asturiano), @tweenjs/tween.js (MIT), kapsule and accessor-fn (MIT, © Vasco Asturiano), and d3 modules (ISC, ©
|
||||
Mike Bostock). Their notices are in `LICENSES-bundled.txt`.
|
||||
|
||||
The service worker leaves this folder out of the offline precache (`service-worker.published.js`): the feed page
|
||||
loads it the first time it shows the globe.
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 698 KiB |
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// Version 2.46.2 globe.gl - https://github.com/vasturiano/globe.gl
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Globe=t()}(this,function(){"use strict";!function(e,t){void 0===t&&(t={});var n=t.insertAt;if("undefined"!=typeof document){var r=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===n&&r.firstChild?r.insertBefore(i,r.firstChild):r.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}(".scene-container .clickable {\n cursor: pointer;\n}");const e="185",t=0,n=1,r=2,i=0,s=1,a=2,o=3,l=1,u=3,c=0,h=1,d=2,A=100,p=101,f=102,m=200,g=201,_=202,v=203,y=204,b=205,x=206,T=207,S=208,w=209,M=210,E=211,C=212,R=213,N=214,P=0,D=1,L=2,I=3,U=4,B=5,O=6,F=7,k=0,V=1,q=2,z=1,G=2,H=3,j=4,W=5,X=6,$=7,Q=301,Y=302,K=303,Z=304,J=306,ee=1e3,te=1001,ne=1002,re=1003,ie=1004,se=1005,ae=1006,oe=1007,le=1008,ue=1009,ce=1010,he=1011,de=1012,Ae=1013,pe=1014,fe=1015,me=1016,ge=1017,_e=1018,ve=1020,ye=35902,be=35899,xe=1021,Te=1022,Se=1023,we=1026,Me=1027,Ee=1028,Ce=1029,Re=1030,Ne=1031,Pe=1032,De=1033,Le=33776,Ie=33777,Ue=33778,Be=33779,Oe=35840,Fe=35841,ke=35842,Ve=35843,qe=36196,ze=37492,Ge=37496,He=37488,je=37489,We=37490,Xe=37491,$e=37808,Qe=37809,Ye=37810,Ke=37811,Ze=37812,Je=37813,et=37814,tt=37815,nt=37816,rt=37817,it=37818,st=37819,at=37820,ot=37821,lt=36492,ut=36494,ct=36495,ht=36283,dt=36284,At=36285,pt=36286,ft="",mt="srgb",gt="srgb-linear",_t="linear",vt="srgb",yt=7680,bt=512,xt=513,Tt=514,St=515,wt=516,Mt=517,Et=518,Ct=519,Rt=35044,Nt=35048,Pt="300 es",Dt=2e3,Lt=2001,It="compute",Ut="render",Bt="depthTextureCompare";function Ot(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}function Ft(e){return document.createElementNS("http://www.w3.org/1999/xhtml",e)}function kt(){const e=Ft("canvas");return e.style.display="block",e}const Vt={};function qt(...e){const t="THREE."+e.shift();console.log(t,...e)}function zt(e){const t=e[0];if("string"==typeof t&&t.startsWith("TSL:")){const t=e[1];t&&t.isStackTrace?e[0]+=" "+t.getLocation():e[1]='Stack trace not available. Enable "THREE.Node.captureStackTrace" to capture stack traces.'}return e}function Gt(...e){const t="THREE."+(e=zt(e)).shift();{const n=e[0];n&&n.isStackTrace?console.warn(n.getError(t)):console.warn(t,...e)}}function Ht(...e){const t="THREE."+(e=zt(e)).shift();{const n=e[0];n&&n.isStackTrace?console.error(n.getError(t)):console.error(t,...e)}}function jt(...e){const t=e.join(" ");t in Vt||(Vt[t]=!0,Gt(...e))}function Wt(){return"undefined"!=typeof self&&void 0!==self.scheduler&&void 0!==self.scheduler.yield?self.scheduler.yield():new Promise(e=>{requestAnimationFrame(e)})}const Xt={[P]:1,[L]:6,[U]:7,[I]:5,[D]:0,[O]:2,[F]:4,[B]:3};class $t{addEventListener(e,t){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[e]&&(n[e]=[]),-1===n[e].indexOf(t)&&n[e].push(t)}hasEventListener(e,t){const n=this._listeners;return void 0!==n&&(void 0!==n[e]&&-1!==n[e].indexOf(t))}removeEventListener(e,t){const n=this._listeners;if(void 0===n)return;const r=n[e];if(void 0!==r){const e=r.indexOf(t);-1!==e&&r.splice(e,1)}}dispatchEvent(e){const t=this._listeners;if(void 0===t)return;const n=t[e.type];if(void 0!==n){e.target=this;const t=n.slice(0);for(let n=0,r=t.length;n<r;n++)t[n].call(this,e);e.target=null}}}const Qt=["00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f","20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f","30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f","40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f","50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f","60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f","70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f","80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f","a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af","b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf","c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf","d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df","e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef","f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff"];let Yt=1234567;const Kt=Math.PI/180,Zt=180/Math.PI;function Jt(){const e=4294967295*Math.random()|0,t=4294967295*Math.random()|0,n=4294967295*Math.random()|0,r=4294967295*Math.random()|0;return(Qt[255&e]+Qt[e>>8&255]+Qt[e>>16&255]+Qt[e>>24&255]+"-"+Qt[255&t]+Qt[t>>8&255]+"-"+Qt[t>>16&15|64]+Qt[t>>24&Line truncated
|
||||
// <http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2)
|
||||
// Analyze the 2 colors and returns the color contrast defined by (WCAG Version 2)
|
||||
T_.readability=function(e,t){var n=T_(e),r=T_(t);return(Math.max(n.getLuminance(),r.getLuminance())+.05)/(Math.min(n.getLuminance(),r.getLuminance())+.05)},T_.isReadable=function(e,t,n){var r,i,s=T_.readability(e,t);switch(i=!1,(r=function(e){var t,n;t=((e=e||{level:"AA",size:"small"}).level||"AA").toUpperCase(),n=(e.size||"small").toLowerCase(),"AA"!==t&&"AAA"!==t&&(t="AA");"small"!==n&&"large"!==n&&(n="small");return{level:t,size:n}}(n)).level+r.size){case"AAsmall":case"AAAlarge":i=s>=4.5;break;case"AAlarge":i=s>=3;break;case"AAAsmall":i=s>=7}return i},T_.mostReadable=function(e,t,n){var r,i,s,a,o=null,l=0;i=(n=n||{}).includeFallbackColors,s=n.level,a=n.size;for(var u=0;u<t.length;u++)(r=T_.readability(e,t[u]))>l&&(l=r,o=T_(t[u]));return T_.isReadable(e,o,{level:s,size:a})||!i?o:(n.includeFallbackColors=!1,T_.mostReadable(e,["#fff","#000"],n))};var V_=T_.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},q_=T_.hexNames=function(e){var t={};for(var n in e)e.hasOwnProperty(n)&&(t[e[n]]=n);return t}(V_);function z_(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}function G_(e,t){(function(e){return"string"==typeof e&&-1!=e.indexOf(".")&&1===parseFloat(e)})(e)&&(e="100%");var n=function(e){return"string"==typeof e&&-1!=e.indexOf("%")}(e);return e=Math.min(t,Math.max(0,parseFloat(e))),n&&(e=parseInt(e*t,10)/100),Math.abs(e-t)<1e-6?1:e%t/parseFloat(t)}function H_(e){return Math.min(1,Math.max(0,e))}function j_(e){return parseInt(e,16)}function W_(e){return 1==e.length?"0"+e:""+e}function X_(e){return e<=1&&(e=100*e+"%"),e}function $_(e){return Math.round(255*parseFloat(e)).toString(16)}function Q_(e){return j_(e)/255}var Y_,K_,Z_,J_=(K_="[\\s|\\(]+("+(Y_="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+Y_+")[,|\\s]+("+Y_+")\\s*\\)?",Z_="[\\s|\\(]+("+Y_+")[,|\\s]+("+Y_+")[,|\\s]+("+Y_+")[,|\\s]+("+Y_+")\\s*\\)?",{CSS_UNIT:new RegExp(Y_),rgb:new RegExp("rgb"+K_),rgba:new RegExp("rgba"+Z_),hsl:new RegExp("hsl"+K_),hsla:new RegExp("hsla"+Z_),hsv:new RegExp("hsv"+K_),hsva:new RegExp("hsva"+Z_),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]Line truncated
|
||||
/*
|
||||
* Copyright 2018-2019, 2022 Uber Technologies, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/var $y={};[["sizeOfH3Index",qy],["sizeOfLatLng",qy],["sizeOfCellBoundary",qy],["sizeOfGeoLoop",qy],["sizeOfGeoPolygon",qy],["sizeOfLinkedGeoPolygon",qy],["sizeOfCoordIJ",qy],["readInt64AsDoubleFromPointer",qy],["isValidCell",qy,[qy,qy]],["isValidIndex",qy,[qy,qy]],["latLngToCell",qy,[qy,qy,qy,qy]],["cellToLatLng",qy,[qy,qy,qy]],["cellToBoundary",qy,[qy,qy,qy]],["maxGridDiskSize",qy,[qy,qy]],["gridDisk",qy,[qy,qy,qy,qy]],["gridDiskDistances",qy,[qy,qy,qy,qy,qy]],["gridRing",qy,[qy,qy,qy,qy]],["gridRingUnsafe",qy,[qy,qy,qy,qy]],["maxPolygonToCellsSize",qy,[qy,qy,qy,qy]],["polygonToCells",qy,[qy,qy,qy,qy]],["maxPolygonToCellsSizeExperimental",qy,[qy,qy,qy,qy]],["polygonToCellsExperimental",qy,[qy,qy,qy,qy,qy,qy]],["cellsToLinkedMultiPolygon",qy,[qy,qy,qy]],["destroyLinkedMultiPolygon",null,[qy]],["compactCells",qy,[qy,qy,qy,qy]],["uncompactCells",qy,[qy,qy,qy,qy,qy,qy]],["uncompactCellsSize",qy,[qy,qy,qy,qy,qy]],["isPentagon",qy,[qy,qy]],["isResClassIII",qy,[qy,qy]],["getBaseCellNumber",qy,[qy,qy]],["getResolution",qy,[qy,qy]],["getIndexDigit",qy,[qy,qy,qy]],["constructCell",qy,[qy,qy,qy,qy]],["maxFaceCount",qy,[qy,qy,qy]],["getIcosahedronFaces",qy,[qy,qy,qy]],["cellToParent",qy,[qy,qy,qy,qy]],["cellToChildren",qy,[qy,qy,qy,qy]],["cellToCenterChild",qy,[qy,qy,qy,qy]],["cellToChildrenSize",qy,[qy,qy,qy,qy]],["cellToChildPos",qy,[qy,qy,qy,qy]],["childPosToCell",qy,[qy,qy,qy,qy,qy,qy]],["areNeighborCells",qy,[qy,qy,qy,qy,qy]],["cellsToDirectedEdge",qy,[qy,qy,qy,qy,qy]],["getDirectedEdgeOrigin",qy,[qy,qy,qy]],["getDirectedEdgeDestination",qy,[qy,qy,qy]],["isValidDirectedEdge",qy,[qy,qy]],["directedEdgeToCells",qy,[qy,qy,qy]],["originToDirectedEdges",qy,[qy,qy,qy]],["directedEdgeToBoundary",qy,[qy,qy,qy]],["reverseDirectedEdge",qy,[qy,qy,qy]],["gridDistance",qy,[qy,qy,qy,qy,qy]],["gridPathCells",qy,[qy,qy,qy,qy,qy]],["gridPathCellsSize",qy,[qy,qy,qy,qy,qy]],["cellToLocalIj",qy,[qy,qy,qy,qy,qy,qy]],["localIjToCell",qy,[qy,qy,qy,qy,qy]],["getHexagonAreaAvgM2",qy,[qy,qy]],["getHexagonAreaAvgKm2",qy,[qy,qy]],["getHexagonEdgeLengthAvgM",qy,[qy,qy]],["getHexagonEdgeLengthAvgKm",qy,[qy,qy]],["greatCircleDistanceM",qy,[qy,qy]],["greatCircleDistanceKm",qy,[qy,qy]],["greatCircleDistanceRads",qy,[qy,qy]],["cellAreaM2",qy,[qy,qy,qy]],["cellAreaKm2",qy,[qy,qy,qy]],["cellAreaRads2",qy,[qy,qy,qy]],["edgeLengthM",qy,[qy,qy,qy]],["edgeLengthKm",qy,[qy,qy,qy]],["edgeLengthRads",qy,[qy,qy,qy]],["getNumCells",qy,[qy,qy]],["getRes0Cells",qy,[qy]],["res0CellCount",qy],["getPentagons",qy,[qy,qy]],["pentagonCount",qy],["cellToVertex",qy,[qy,qy,qy,qy]],["cellToVertexes",qy,[qy,qy,qy]],["vertexToLatLng",qy,[qy,qy,qy]],["isValidVertex",qy,[qy,qy]]].forEach(function(e){$y[e[0]]=Vy.cwrap.apply(Vy,e)});var Qy=16,Yy=$y.sizeOfH3Index(),Ky=$y.sizeOfLatLng(),Zy=$y.sizeOfCellBoundary(),Jy=$y.sizeOfGeoPolygon(),eb=$y.sizeOfGeoLoop();$y.sizeOfLinkedGeoPolygon(),$y.sizeOfCoordIJ();var tb=Math.pow(2,32)-1;var nb=/[^0-9a-fA-F]/;function rb(e){if(Array.isArray(e)&&2===e.length&&Number.isInteger(e[0])&&Number.isInteger(e[1]))return e;if("string"!=typeof e||nb.test(e))return[0,0];var t=parseInt(e.substring(0,e.length-8),Qy);return[parseInt(e.substring(e.length-8),Qy),t]}function ib(e){if(e>=0)return e.toString(Qy);var t=sb(8,(e&=2147483647).toString(Qy));return t=(parseInt(t[0],Qy)+8).toString(Qy)+t.substring(1)}function sb(e,t){for(var n=e-t.length,r="",i=0;i<n;i++)r+="0";return r+=t}function ab(e,t,n){for(var r=e.length,i=Vy._calloc(r,Ky),s=n?1:0,a=n?0:1,o=0;o<2*r;o+=2)Vy.HEAPF64.set([e[o/2][s],e[o/2][a]].map(pb),i/8+o);return Vy.HEAPU32.set([r,i],t/4),t}function ob(e,t){void 0===t&&(t=0);var n=Vy.getValue(e+Yy*t,"i32"),r=Vy.getValue(e+Yy*t+4,"i32");return r?function(e,t){return ib(t)+sb(8,ib(e))}(n,r):null}function lb(e){return 180*Vy.getValue(e,"double")/Math.PI}function ub(e){return[lb(e+8),lb(e)]}function cb(e,t,n){var r=Vy._malloc(Ky);Vy.HEAPF64.set([e,t].map(pb),r/8);var i=Vy._malloc(Yy);try{return Xy($y.latLngToCell(r,n,i)),function(e){if(!e)throw Wy(1002);return e}(ob(i))}finally{Vy._free(i),Vy._free(r)}}function hb(e){var t,n=Vy._malloc(Ky),r=rb(e),i=r[0],s=r[1];try{return Xy($y.cellToLatLng(i,s,n)),[lb(t=n),lb(t+8)]}finally{Vy._free(n)}}function db(e,t){var n=Vy._malloc(Zy),r=rb(e),i=r[0],s=r[1];try{return Xy($y.cellToBoundary(i,s,n)),function(e){for(var t=Vy.getValue(e,"i32"),n=e+8,r=[],i=ub,s=0;s<2*t;s+=2)r.push(i(n+8*s));return r.push(r[0]),r}(n)}finally{Vy._free(n)}}function Ab(e,t,n){if(function(e){if("number"!=typeof e||e<0||e>15||Math.floor(e)!==e)throw jy(4,e)}(t),n=Boolean(n),0===e.length||0===e[0].length)return[];var r,i=function(e,t){var n,r=e.length-1,i=Vy._calloc(Jy),s=0+eb,a=s+4;if(ab(e[0],i+0,t),r>0){n=Vy._calloc(r,eb);for(var o=0;o<r;o++)ab(e[o+1],n+eb*o,t)}return Vy.setValue(i+s,r,"i32"),Vy.setValue(i+a,n,"i32"),i}("number"==typeof e[0][0]?[e]:e,n),s=Vy._malloc(8);try{Xy($y.maxPolygonToCellsSize(i,t,0,s));var a=function(e){if(e>tb)throw Wy(1001,e);return e}((r=s,$y.readInt64AsDoubleFromPointer(r))),o=Vy._calloc(a,Yy);try{rLine truncated
|
||||
Reference in new issue
Block a user