WIP authoring app and Visual Studio support added
This commit is contained in:
@@ -155,6 +155,47 @@ public class MainViewModelTests
|
||||
Assert.DoesNotContain(@"\\", viewModel.TomlPreview, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generated_shortcut_tracks_app_info_and_allows_description_override()
|
||||
{
|
||||
var viewModel = new MainViewModel(() => null)
|
||||
{
|
||||
AppName = "Renamed App",
|
||||
ApplicationFolder = "RenamedApp",
|
||||
InstallRootToken = "{ProgramFilesX64}",
|
||||
PrimaryPayload = @"payload\renamed.exe"
|
||||
};
|
||||
|
||||
var defaultDocument = viewModel.BuildDocument();
|
||||
var defaultShortcut = Assert.Single(defaultDocument.Shortcuts);
|
||||
Assert.Equal(@"{Desktop}\RenamedApp.lnk", defaultShortcut.Path);
|
||||
Assert.Equal(@"{ProgramFilesX64}\RenamedApp\bin\renamed.exe", defaultShortcut.Target);
|
||||
Assert.Equal(@"{ProgramFilesX64}\RenamedApp", defaultShortcut.WorkingDirectory);
|
||||
Assert.Equal("Launch Renamed App", defaultShortcut.Description);
|
||||
|
||||
viewModel.ShortcutDescription = "Start Renamed App";
|
||||
|
||||
var customShortcut = Assert.Single(viewModel.BuildDocument().Shortcuts);
|
||||
Assert.Equal("Start Renamed App", customShortcut.Description);
|
||||
Assert.Contains("description = 'Start Renamed App'", viewModel.TomlPreview);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Purge_registry_branches_track_registry_entries()
|
||||
{
|
||||
var viewModel = new MainViewModel(() => null)
|
||||
{
|
||||
ApplicationFolder = "RenamedApp"
|
||||
};
|
||||
viewModel.AddRegistry(@"HKLM\Software\Vendor\RenamedApp", "InstallRoot", @"{ProgramFilesX64}\RenamedApp");
|
||||
|
||||
var branches = viewModel.BuildDocument().Purge.RegistryBranches;
|
||||
|
||||
Assert.Contains(@"HKCU\Software\RenamedApp", branches);
|
||||
Assert.Contains(@"HKLM\Software\Vendor\RenamedApp", branches);
|
||||
Assert.Contains(@"HKLM\Software\Vendor\RenamedApp", viewModel.PurgeRegistryBranchesPreview);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Packaging_is_disabled_when_covenant_setup_is_missing()
|
||||
{
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 1,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "Program.cs",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\Program.cs",
|
||||
"RelativeDocumentMoniker": "Program.cs",
|
||||
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\Program.cs",
|
||||
"RelativeToolTip": "Program.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-05T00:32:51.733Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "App.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\App.xaml",
|
||||
"RelativeDocumentMoniker": "App.xaml",
|
||||
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\App.xaml",
|
||||
"RelativeToolTip": "App.xaml",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
|
||||
"WhenOpened": "2026-05-05T00:30:19.531Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 1,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "Program.cs",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\Program.cs",
|
||||
"RelativeDocumentMoniker": "Program.cs",
|
||||
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\Program.cs",
|
||||
"RelativeToolTip": "Program.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-05T00:32:51.733Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "App.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\App.xaml",
|
||||
"RelativeDocumentMoniker": "App.xaml",
|
||||
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\App.xaml",
|
||||
"RelativeToolTip": "App.xaml",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
|
||||
"WhenOpened": "2026-05-05T00:30:19.531Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,11 +4,11 @@
|
||||
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<WindowsAppSDKSelfContained Condition="'$(RuntimeIdentifier)' != ''">true</WindowsAppSDKSelfContained>
|
||||
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == ''">win-x64</RuntimeIdentifier>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSdkBootstrapInitialize>false</WindowsAppSdkBootstrapInitialize>
|
||||
<WindowsAppSdkDeploymentManagerInitialize>false</WindowsAppSdkDeploymentManagerInitialize>
|
||||
<WindowsAppSdkUndockedRegFreeWinRTInitialize Condition="'$(RuntimeIdentifier)' != ''">true</WindowsAppSdkUndockedRegFreeWinRTInitialize>
|
||||
<WindowsAppSdkUndockedRegFreeWinRTInitialize Condition="'$(RuntimeIdentifier)' == ''">false</WindowsAppSdkUndockedRegFreeWinRTInitialize>
|
||||
<WindowsAppSdkUndockedRegFreeWinRTInitialize>true</WindowsAppSdkUndockedRegFreeWinRTInitialize>
|
||||
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.5.11723.231 stable
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Covenant.Setup.Authoring", "Covenant.Setup.Authoring.csproj", "{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A95CF113-E203-4D42-901D-77B6B1147DBB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -11,6 +11,11 @@ internal sealed record PackageResult(
|
||||
string Output,
|
||||
string Error);
|
||||
|
||||
internal sealed record ToolValidationResult(
|
||||
bool IsValid,
|
||||
CovenantSetupTool? Tool,
|
||||
string Message);
|
||||
|
||||
internal static class CovenantSetupToolLocator
|
||||
{
|
||||
public static CovenantSetupTool? Find()
|
||||
@@ -82,6 +87,130 @@ internal static class CovenantSetupToolLocator
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CovenantSetupToolValidator
|
||||
{
|
||||
public static async Task<ToolValidationResult> ValidateAsync(
|
||||
string path,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return Invalid("Pick a valid covenant-setup executable.");
|
||||
}
|
||||
|
||||
string fullPath;
|
||||
try
|
||||
{
|
||||
fullPath = System.IO.Path.GetFullPath(path.Trim());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Invalid("The covenant-setup path is invalid: " + ex.Message);
|
||||
}
|
||||
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
return Invalid("Pick a valid covenant-setup executable.");
|
||||
}
|
||||
|
||||
var output = new StringBuilder();
|
||||
var error = new StringBuilder();
|
||||
using var process = new Process
|
||||
{
|
||||
StartInfo = CreateHelpStartInfo(fullPath),
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
process.OutputDataReceived += (_, args) =>
|
||||
{
|
||||
if (args.Data is not null)
|
||||
{
|
||||
output.AppendLine(args.Data);
|
||||
}
|
||||
};
|
||||
process.ErrorDataReceived += (_, args) =>
|
||||
{
|
||||
if (args.Data is not null)
|
||||
{
|
||||
error.AppendLine(args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
|
||||
try
|
||||
{
|
||||
if (!process.Start())
|
||||
{
|
||||
return Invalid("Pick a valid covenant-setup executable.");
|
||||
}
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
await process.WaitForExitAsync(timeout.Token);
|
||||
process.WaitForExit();
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
TryKill(process);
|
||||
return Invalid("The selected executable did not respond to the covenant-setup help command.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Invalid("The selected executable could not be started: " + ex.Message);
|
||||
}
|
||||
|
||||
var combinedOutput = string.Join(
|
||||
Environment.NewLine,
|
||||
new[] { output.ToString(), error.ToString() }.Where(part => !string.IsNullOrWhiteSpace(part)));
|
||||
if (process.ExitCode == 0 && LooksLikeCovenantSetupHelp(combinedOutput))
|
||||
{
|
||||
return new ToolValidationResult(true, new CovenantSetupTool(fullPath), "Packaging enabled: " + fullPath);
|
||||
}
|
||||
|
||||
return Invalid("Pick a valid covenant-setup executable.");
|
||||
}
|
||||
|
||||
private static ProcessStartInfo CreateHelpStartInfo(string path)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
startInfo.ArgumentList.Add("--help");
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
private static bool LooksLikeCovenantSetupHelp(string output) =>
|
||||
!string.IsNullOrWhiteSpace(output) &&
|
||||
(output.Contains("covenant", StringComparison.OrdinalIgnoreCase) ||
|
||||
(output.Contains("package", StringComparison.OrdinalIgnoreCase) &&
|
||||
output.Contains("install", StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
private static ToolValidationResult Invalid(string message) =>
|
||||
new(false, null, message);
|
||||
|
||||
private static void TryKill(Process process)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill(entireProcessTree: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort; the caller reports validation failure either way.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CovenantSetupPackager
|
||||
{
|
||||
public static async Task<PackageResult> PackageAsync(
|
||||
|
||||
+255
-26
@@ -13,6 +13,7 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
private string _applicationFolder = "CovenantSetupSample";
|
||||
private string _primaryPayload = @"payload\sample_app.cmd";
|
||||
private string _outputDirectory = Path.Combine(Environment.CurrentDirectory, "dist");
|
||||
private string? _shortcutDescriptionOverride;
|
||||
private CovenantSetupTool? _covenantSetupTool;
|
||||
private string _covenantSetupStatus = "covenant-setup.exe was not found. Packaging is disabled.";
|
||||
private string _tomlPreview = string.Empty;
|
||||
@@ -69,6 +70,29 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
set => SetProperty(ref _primaryPayload, value);
|
||||
}
|
||||
|
||||
public string ShortcutDescription
|
||||
{
|
||||
get => EffectiveShortcutDescription();
|
||||
set
|
||||
{
|
||||
var normalized = string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
var defaultDescription = DefaultShortcutDescription(AppName.Trim());
|
||||
var overrideValue = string.Equals(normalized, defaultDescription, StringComparison.Ordinal)
|
||||
? null
|
||||
: normalized;
|
||||
|
||||
if (string.Equals(_shortcutDescriptionOverride, overrideValue, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_shortcutDescriptionOverride = overrideValue;
|
||||
OnPropertyChanged();
|
||||
NotifyGeneratedManifestFieldsChanged();
|
||||
RefreshPreview();
|
||||
}
|
||||
}
|
||||
|
||||
public string OutputDirectory
|
||||
{
|
||||
get => _outputDirectory;
|
||||
@@ -83,6 +107,7 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
if (SetProperty(ref _covenantSetupTool, value, refreshPreview: false))
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasCovenantSetupTool)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CovenantSetupPath)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CanPackage)));
|
||||
}
|
||||
}
|
||||
@@ -90,6 +115,8 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
|
||||
public bool HasCovenantSetupTool => CovenantSetupTool is not null;
|
||||
|
||||
public string CovenantSetupPath => CovenantSetupTool?.Path ?? string.Empty;
|
||||
|
||||
public string CovenantSetupStatus
|
||||
{
|
||||
get => _covenantSetupStatus;
|
||||
@@ -125,6 +152,16 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
public string ShortcutPath => GeneratedShortcut()?.Path ?? string.Empty;
|
||||
|
||||
public string ShortcutTarget => GeneratedShortcut()?.Target ?? string.Empty;
|
||||
|
||||
public string ShortcutWorkingDirectory => GeneratedShortcut()?.WorkingDirectory ?? string.Empty;
|
||||
|
||||
public string PurgePathsPreview => JoinLines(BuildDocument().Purge.Paths);
|
||||
|
||||
public string PurgeRegistryBranchesPreview => JoinLines(BuildDocument().Purge.RegistryBranches);
|
||||
|
||||
public ObservableCollection<string> Directories { get; } = [];
|
||||
public ObservableCollection<FileSpec> Files { get; } = [];
|
||||
public ObservableCollection<RegistrySpec> Registry { get; } = [];
|
||||
@@ -135,12 +172,23 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
|
||||
public void RefreshCovenantSetupTool()
|
||||
{
|
||||
CovenantSetupTool = _locateCovenantSetupTool();
|
||||
SetCovenantSetupTool(_locateCovenantSetupTool());
|
||||
}
|
||||
|
||||
public void SetCovenantSetupTool(CovenantSetupTool? tool)
|
||||
{
|
||||
CovenantSetupTool = tool;
|
||||
CovenantSetupStatus = CovenantSetupTool is null
|
||||
? "covenant-setup.exe was not found. Packaging is disabled."
|
||||
: "Packaging enabled: " + CovenantSetupTool.Path;
|
||||
}
|
||||
|
||||
public void RejectCovenantSetupTool(string message)
|
||||
{
|
||||
CovenantSetupTool = null;
|
||||
CovenantSetupStatus = message;
|
||||
}
|
||||
|
||||
public void AddDirectory(string path)
|
||||
{
|
||||
AddTrimmed(Directories, path, value => value);
|
||||
@@ -206,18 +254,23 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
public ManifestDocument BuildDocument()
|
||||
{
|
||||
var template = BuildTemplateEntries();
|
||||
var directories = CombineUnique(template.Directories, Directories);
|
||||
var files = CombineUnique(template.Files, Files);
|
||||
var registry = CombineUnique(template.Registry, Registry);
|
||||
var shortcuts = CombineUnique(BuildShortcutEntries(files), Shortcuts);
|
||||
var purgePaths = CombineUnique(BuildPurgePaths(files, registry), PurgePaths);
|
||||
return new ManifestDocument
|
||||
{
|
||||
AppName = AppName.Trim(),
|
||||
Directories = CombineUnique(template.Directories, Directories),
|
||||
Files = CombineUnique(template.Files, Files),
|
||||
Registry = CombineUnique(template.Registry, Registry),
|
||||
Shortcuts = CombineUnique(template.Shortcuts, Shortcuts),
|
||||
Directories = directories,
|
||||
Files = files,
|
||||
Registry = registry,
|
||||
Shortcuts = shortcuts,
|
||||
Scripts = Scripts.ToArray(),
|
||||
Purge = new PurgeSpec
|
||||
{
|
||||
Paths = CombineUnique(template.PurgePaths, PurgePaths),
|
||||
RegistryBranches = CombineUnique(template.PurgeRegistryBranches, PurgeRegistryBranches)
|
||||
Paths = purgePaths,
|
||||
RegistryBranches = CombineUnique(registry.Select(entry => entry.Key), PurgeRegistryBranches)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -308,15 +361,22 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
}
|
||||
|
||||
field = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
OnPropertyChanged(propertyName);
|
||||
if (propertyName == nameof(AppName))
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ExpectedManifestFileName)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ManifestSubtitle)));
|
||||
OnPropertyChanged(nameof(ExpectedManifestFileName));
|
||||
OnPropertyChanged(nameof(ManifestSubtitle));
|
||||
}
|
||||
if (propertyName == nameof(OutputDirectory))
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CanPackage)));
|
||||
OnPropertyChanged(nameof(CanPackage));
|
||||
}
|
||||
if (propertyName is nameof(AppName)
|
||||
or nameof(InstallRootToken)
|
||||
or nameof(ApplicationFolder)
|
||||
or nameof(PrimaryPayload))
|
||||
{
|
||||
NotifyGeneratedManifestFieldsChanged();
|
||||
}
|
||||
if (refreshPreview)
|
||||
{
|
||||
@@ -327,9 +387,43 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
|
||||
private void CollectionChanged(object? sender, NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
if (ReferenceEquals(sender, Files))
|
||||
{
|
||||
OnPropertyChanged(nameof(ShortcutPath));
|
||||
OnPropertyChanged(nameof(ShortcutTarget));
|
||||
OnPropertyChanged(nameof(ShortcutWorkingDirectory));
|
||||
OnPropertyChanged(nameof(PurgePathsPreview));
|
||||
}
|
||||
if (ReferenceEquals(sender, Registry) || ReferenceEquals(sender, PurgePaths))
|
||||
{
|
||||
OnPropertyChanged(nameof(PurgePathsPreview));
|
||||
}
|
||||
if (ReferenceEquals(sender, Registry) || ReferenceEquals(sender, PurgeRegistryBranches))
|
||||
{
|
||||
OnPropertyChanged(nameof(PurgeRegistryBranchesPreview));
|
||||
}
|
||||
|
||||
RefreshPreview();
|
||||
}
|
||||
|
||||
private void NotifyGeneratedManifestFieldsChanged()
|
||||
{
|
||||
OnPropertyChanged(nameof(ShortcutDescription));
|
||||
OnPropertyChanged(nameof(ShortcutPath));
|
||||
OnPropertyChanged(nameof(ShortcutTarget));
|
||||
OnPropertyChanged(nameof(ShortcutWorkingDirectory));
|
||||
OnPropertyChanged(nameof(PurgePathsPreview));
|
||||
OnPropertyChanged(nameof(PurgeRegistryBranchesPreview));
|
||||
}
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (propertyName is not null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTrimmed<T>(
|
||||
ObservableCollection<T> collection,
|
||||
string value,
|
||||
@@ -352,6 +446,154 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
private static string? NullIfWhiteSpace(string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
|
||||
private string EffectiveShortcutDescription() =>
|
||||
_shortcutDescriptionOverride ?? DefaultShortcutDescription(AppName.Trim());
|
||||
|
||||
private string ManifestShortcutDescription() =>
|
||||
NullIfWhiteSpace(EffectiveShortcutDescription()) ?? DefaultShortcutDescription(AppName.Trim());
|
||||
|
||||
private static string DefaultShortcutDescription(string appName) =>
|
||||
string.IsNullOrWhiteSpace(appName) ? "Launch application" : "Launch " + appName;
|
||||
|
||||
private ShortcutSpec? GeneratedShortcut()
|
||||
{
|
||||
var template = BuildTemplateEntries();
|
||||
var files = CombineUnique(template.Files, Files);
|
||||
return BuildShortcutEntries(files).FirstOrDefault();
|
||||
}
|
||||
|
||||
private static string JoinLines(IEnumerable<string> values) =>
|
||||
string.Join(Environment.NewLine, values);
|
||||
|
||||
private IReadOnlyList<ShortcutSpec> BuildShortcutEntries(IReadOnlyList<FileSpec> files)
|
||||
{
|
||||
var target = files
|
||||
.Select(file => file.Destination.Trim())
|
||||
.FirstOrDefault(destination => !string.IsNullOrWhiteSpace(destination));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(target))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return
|
||||
[
|
||||
new ShortcutSpec(
|
||||
@"{Desktop}\" + ShortcutFileName(target) + ".lnk",
|
||||
target,
|
||||
null,
|
||||
ParentPath(target),
|
||||
ManifestShortcutDescription())
|
||||
];
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> BuildPurgePaths(
|
||||
IReadOnlyList<FileSpec> files,
|
||||
IReadOnlyList<RegistrySpec> registry)
|
||||
{
|
||||
var candidates = new List<string>();
|
||||
foreach (var entry in registry)
|
||||
{
|
||||
var value = entry.Value.Trim();
|
||||
if (IsManifestPath(value) && !IsUnsafePurgeRoot(value))
|
||||
{
|
||||
candidates.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var parent = ParentPath(file.Destination);
|
||||
if (!string.IsNullOrWhiteSpace(parent) && !IsUnsafePurgeRoot(parent))
|
||||
{
|
||||
candidates.Add(parent);
|
||||
}
|
||||
}
|
||||
|
||||
return RemoveCoveredChildPaths(CombineUnique(candidates, Array.Empty<string>()));
|
||||
}
|
||||
|
||||
private string ShortcutFileName(string target)
|
||||
{
|
||||
var fileName = FileNameWithoutExtension(target);
|
||||
var sanitized = SanitizeIdentifier(fileName);
|
||||
if (!string.IsNullOrWhiteSpace(sanitized))
|
||||
{
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
var fallbackName = string.IsNullOrWhiteSpace(ApplicationFolder)
|
||||
? AppName
|
||||
: ApplicationFolder;
|
||||
return SanitizeIdentifier(fallbackName);
|
||||
}
|
||||
|
||||
private static string FileNameWithoutExtension(string path)
|
||||
{
|
||||
var fileName = path.Trim().TrimEnd('\\');
|
||||
var separatorIndex = fileName.LastIndexOf('\\');
|
||||
if (separatorIndex >= 0 && separatorIndex < fileName.Length - 1)
|
||||
{
|
||||
fileName = fileName[(separatorIndex + 1)..];
|
||||
}
|
||||
|
||||
var extensionIndex = fileName.LastIndexOf('.');
|
||||
return extensionIndex > 0 ? fileName[..extensionIndex] : fileName;
|
||||
}
|
||||
|
||||
private static string? ParentPath(string path)
|
||||
{
|
||||
var trimmed = path.Trim().TrimEnd('\\');
|
||||
var separatorIndex = trimmed.LastIndexOf('\\');
|
||||
if (separatorIndex < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (separatorIndex == 2 && trimmed[1] == ':')
|
||||
{
|
||||
return trimmed[..(separatorIndex + 1)];
|
||||
}
|
||||
|
||||
return separatorIndex == 0 ? null : trimmed[..separatorIndex];
|
||||
}
|
||||
|
||||
private static bool IsManifestPath(string value) =>
|
||||
ManifestTokens.KnownPathTokens.Any(token =>
|
||||
value.StartsWith(token + @"\", StringComparison.OrdinalIgnoreCase)) ||
|
||||
Path.IsPathRooted(value);
|
||||
|
||||
private static bool IsUnsafePurgeRoot(string value)
|
||||
{
|
||||
var normalized = value.Trim().TrimEnd('\\');
|
||||
if (ManifestTokens.KnownPathTokens.Any(token =>
|
||||
string.Equals(normalized, token, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return normalized.Length == 2 &&
|
||||
normalized[1] == ':' &&
|
||||
IsAsciiLetter(normalized[0]);
|
||||
}
|
||||
|
||||
private static bool IsAsciiLetter(char value) =>
|
||||
value is >= 'A' and <= 'Z' or >= 'a' and <= 'z';
|
||||
|
||||
private static IReadOnlyList<string> RemoveCoveredChildPaths(IReadOnlyList<string> paths) =>
|
||||
paths
|
||||
.Where(path => !paths.Any(parent =>
|
||||
!string.Equals(parent, path, StringComparison.OrdinalIgnoreCase) &&
|
||||
IsParentPath(parent, path)))
|
||||
.ToArray();
|
||||
|
||||
private static bool IsParentPath(string parent, string child)
|
||||
{
|
||||
var normalizedParent = parent.TrimEnd('\\');
|
||||
return child.Length > normalizedParent.Length &&
|
||||
child.StartsWith(normalizedParent + "\\", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsSupportedRegistryRoot(string value) =>
|
||||
value.StartsWith(@"HKCU\", StringComparison.OrdinalIgnoreCase) ||
|
||||
value.StartsWith(@"HKLM\", StringComparison.OrdinalIgnoreCase);
|
||||
@@ -461,7 +703,6 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
var root = InstallRootToken.TrimEnd('\\') + "\\" + folder;
|
||||
var registryBranch = @"HKCU\Software\" + folder;
|
||||
var files = new List<FileSpec>();
|
||||
var shortcuts = new List<ShortcutSpec>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(PrimaryPayload))
|
||||
{
|
||||
@@ -471,22 +712,13 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
{
|
||||
var destination = root + @"\bin\" + fileName;
|
||||
files.Add(new FileSpec(source, destination));
|
||||
shortcuts.Add(new ShortcutSpec(
|
||||
@"{Desktop}\" + folder + ".lnk",
|
||||
destination,
|
||||
null,
|
||||
root,
|
||||
"Launch " + appName));
|
||||
}
|
||||
}
|
||||
|
||||
return new TemplateManifestEntries(
|
||||
[root, root + @"\bin"],
|
||||
files,
|
||||
[new RegistrySpec(registryBranch, "InstallRoot", root)],
|
||||
shortcuts,
|
||||
[root],
|
||||
[registryBranch]);
|
||||
[new RegistrySpec(registryBranch, "InstallRoot", root)]);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<T> CombineUnique<T>(IEnumerable<T> first, IEnumerable<T> second)
|
||||
@@ -506,8 +738,5 @@ internal sealed partial class MainViewModel : INotifyPropertyChanged
|
||||
private sealed record TemplateManifestEntries(
|
||||
IReadOnlyList<string> Directories,
|
||||
IReadOnlyList<FileSpec> Files,
|
||||
IReadOnlyList<RegistrySpec> Registry,
|
||||
IReadOnlyList<ShortcutSpec> Shortcuts,
|
||||
IReadOnlyList<string> PurgePaths,
|
||||
IReadOnlyList<string> PurgeRegistryBranches);
|
||||
IReadOnlyList<RegistrySpec> Registry);
|
||||
}
|
||||
|
||||
+335
-104
@@ -3,6 +3,7 @@ using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
@@ -24,7 +25,12 @@ internal sealed class MainWindow : Window
|
||||
private readonly List<TextBlock> _secondaryTextBlocks = [];
|
||||
private TextBlock _statusText = null!;
|
||||
private TextBlock _toolStatusText = null!;
|
||||
private TextBlock _installerDialogMessageText = null!;
|
||||
private TextBox _previewBox = null!;
|
||||
private Button _copyPreviewButton = null!;
|
||||
private TextBox _covenantSetupPathBox = null!;
|
||||
private Button _browseCovenantSetupButton = null!;
|
||||
private Button _refreshToolButton = null!;
|
||||
private TextBox _outputDirectoryBox = null!;
|
||||
private Button _chooseOutputButton = null!;
|
||||
private Button _generateInstallerButton = null!;
|
||||
@@ -32,8 +38,10 @@ internal sealed class MainWindow : Window
|
||||
private ToggleSwitch _themeToggle = null!;
|
||||
private string? _lastSavedManifestPath;
|
||||
private bool _isPackaging;
|
||||
private int _copyPreviewFeedbackVersion;
|
||||
private bool _syncingThemeToggle;
|
||||
private bool _usingSystemTheme;
|
||||
private bool _syncingCovenantSetupPath;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@@ -50,6 +58,7 @@ internal sealed class MainWindow : Window
|
||||
if (args.PropertyName is nameof(MainViewModel.HasCovenantSetupTool)
|
||||
or nameof(MainViewModel.CanPackage)
|
||||
or nameof(MainViewModel.CovenantSetupStatus)
|
||||
or nameof(MainViewModel.CovenantSetupPath)
|
||||
or nameof(MainViewModel.OutputDirectory))
|
||||
{
|
||||
RefreshPackageControls();
|
||||
@@ -143,19 +152,40 @@ internal sealed class MainWindow : Window
|
||||
Spacing = 8
|
||||
};
|
||||
|
||||
var themeControl = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Spacing = 6
|
||||
};
|
||||
themeControl.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "Light",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
|
||||
_themeToggle = new ToggleSwitch
|
||||
{
|
||||
OnContent = "Dark",
|
||||
OffContent = "Light",
|
||||
MinWidth = 96
|
||||
OffContent = string.Empty,
|
||||
MinWidth = 72
|
||||
};
|
||||
_themeToggle.Toggled += (_, _) => ToggleTheme();
|
||||
actions.Children.Add(_themeToggle);
|
||||
themeControl.Children.Add(_themeToggle);
|
||||
actions.Children.Add(themeControl);
|
||||
|
||||
var validateButton = new Button { Content = "Validate", MinWidth = 92 };
|
||||
validateButton.Click += async (_, _) => await ShowValidationAsync();
|
||||
actions.Children.Add(validateButton);
|
||||
|
||||
var installerConfigButton = new Button { Content = "Installer Config", MinWidth = 124 };
|
||||
installerConfigButton.Click += async (_, _) => await ShowInstallerConfigAsync();
|
||||
actions.Children.Add(installerConfigButton);
|
||||
|
||||
_generateInstallerButton = new Button { Content = "Save and Build", MinWidth = 124 };
|
||||
_generateInstallerButton.Click += async (_, _) => await GenerateInstallerAsync();
|
||||
actions.Children.Add(_generateInstallerButton);
|
||||
|
||||
var saveButton = new Button { Content = "Save TOML", MinWidth = 104 };
|
||||
saveButton.Click += async (_, _) => await SaveManifestAsync();
|
||||
actions.Children.Add(saveButton);
|
||||
@@ -169,13 +199,11 @@ internal sealed class MainWindow : Window
|
||||
{
|
||||
var stack = new StackPanel { Spacing = 12 };
|
||||
stack.Children.Add(BuildAppSection());
|
||||
stack.Children.Add(BuildPackageSection());
|
||||
stack.Children.Add(BuildDirectoriesSection());
|
||||
stack.Children.Add(BuildFilesSection());
|
||||
stack.Children.Add(BuildRegistrySection());
|
||||
stack.Children.Add(BuildShortcutsSection());
|
||||
stack.Children.Add(BuildScriptsSection());
|
||||
stack.Children.Add(BuildPurgeSection());
|
||||
|
||||
return new ScrollViewer
|
||||
{
|
||||
@@ -202,10 +230,11 @@ internal sealed class MainWindow : Window
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
|
||||
var copyButton = new Button { Content = "Copy", MinWidth = 80 };
|
||||
copyButton.Click += async (_, _) => await CopyPreviewAsync();
|
||||
Grid.SetColumn(copyButton, 1);
|
||||
header.Children.Add(copyButton);
|
||||
_copyPreviewButton = new Button { Content = "Copy", MinWidth = 80 };
|
||||
AutomationProperties.SetName(_copyPreviewButton, "Copy TOML preview");
|
||||
_copyPreviewButton.Click += async (_, _) => await CopyPreviewAsync();
|
||||
Grid.SetColumn(_copyPreviewButton, 1);
|
||||
header.Children.Add(_copyPreviewButton);
|
||||
|
||||
Grid.SetRow(header, 0);
|
||||
preview.Children.Add(header);
|
||||
@@ -262,7 +291,7 @@ internal sealed class MainWindow : Window
|
||||
Labeled("Primary Payload", payloadBox));
|
||||
}
|
||||
|
||||
private FrameworkElement BuildPackageSection()
|
||||
private FrameworkElement BuildInstallerConfigContent()
|
||||
{
|
||||
_toolStatusText = new TextBlock
|
||||
{
|
||||
@@ -276,28 +305,52 @@ internal sealed class MainWindow : Window
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
_covenantSetupPathBox = new TextBox
|
||||
{
|
||||
PlaceholderText = @"C:\path\to\covenant-setup.exe",
|
||||
Text = _viewModel.CovenantSetupPath
|
||||
};
|
||||
_covenantSetupPathBox.TextChanged += (_, _) =>
|
||||
{
|
||||
if (_syncingCovenantSetupPath)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(_covenantSetupPathBox.Text, _viewModel.CovenantSetupPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_viewModel.RejectCovenantSetupTool("covenant-setup.exe was not validated. Packaging is disabled.");
|
||||
}
|
||||
};
|
||||
_covenantSetupPathBox.LostFocus += async (_, _) => await ValidateTypedCovenantSetupPathAsync(showInvalidMessage: true);
|
||||
|
||||
_browseCovenantSetupButton = new Button
|
||||
{
|
||||
Content = "Browse",
|
||||
Width = 96
|
||||
};
|
||||
_browseCovenantSetupButton.Click += async (_, _) => await BrowseCovenantSetupPathAsync();
|
||||
|
||||
_outputDirectoryBox = BoundTextBox(nameof(MainViewModel.OutputDirectory), @"C:\path\to\dist");
|
||||
|
||||
_chooseOutputButton = new Button
|
||||
{
|
||||
Content = "Choose",
|
||||
MinWidth = 84
|
||||
Width = 96
|
||||
};
|
||||
_chooseOutputButton.Click += async (_, _) => await ChooseOutputDirectoryAsync();
|
||||
|
||||
var refreshButton = new Button
|
||||
_refreshToolButton = new Button
|
||||
{
|
||||
Content = "Refresh Tool Check",
|
||||
MinWidth = 140
|
||||
Width = 156
|
||||
};
|
||||
refreshButton.Click += (_, _) => _viewModel.RefreshCovenantSetupTool();
|
||||
|
||||
_generateInstallerButton = new Button
|
||||
_refreshToolButton.Click += (_, _) =>
|
||||
{
|
||||
Content = "Generate Installer EXE",
|
||||
MinWidth = 168
|
||||
_viewModel.RefreshCovenantSetupTool();
|
||||
SyncCovenantSetupPathBox();
|
||||
SetInstallerDialogMessage(string.Empty);
|
||||
};
|
||||
_generateInstallerButton.Click += async (_, _) => await GenerateInstallerAsync();
|
||||
|
||||
var actionRow = new StackPanel
|
||||
{
|
||||
@@ -305,14 +358,28 @@ internal sealed class MainWindow : Window
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Spacing = 8
|
||||
};
|
||||
actionRow.Children.Add(refreshButton);
|
||||
actionRow.Children.Add(_generateInstallerButton);
|
||||
actionRow.Children.Add(_refreshToolButton);
|
||||
|
||||
return Section(
|
||||
"Installer EXE",
|
||||
_toolStatusText,
|
||||
Labeled("Output Directory", InputRow(_outputDirectoryBox, _chooseOutputButton)),
|
||||
actionRow);
|
||||
_installerDialogMessageText = new TextBlock
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
MaxLines = 6
|
||||
};
|
||||
_secondaryTextBlocks.Add(_installerDialogMessageText);
|
||||
|
||||
var stack = new StackPanel
|
||||
{
|
||||
Spacing = 10,
|
||||
MinWidth = 640,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
stack.Children.Add(_toolStatusText);
|
||||
stack.Children.Add(DialogFieldGrid(
|
||||
("Covenant Setup Executable", _covenantSetupPathBox, _browseCovenantSetupButton),
|
||||
("Output Directory", _outputDirectoryBox, _chooseOutputButton)));
|
||||
stack.Children.Add(actionRow);
|
||||
stack.Children.Add(_installerDialogMessageText);
|
||||
return stack;
|
||||
}
|
||||
|
||||
private FrameworkElement BuildDirectoriesSection()
|
||||
@@ -391,41 +458,11 @@ internal sealed class MainWindow : Window
|
||||
|
||||
private FrameworkElement BuildShortcutsSection()
|
||||
{
|
||||
var pathBox = new TextBox { PlaceholderText = @"{Desktop}\VendorApp.lnk" };
|
||||
var targetBox = new TextBox { PlaceholderText = @"{LocalAppData}\Vendor\App\bin\app.exe" };
|
||||
var argumentsBox = new TextBox { PlaceholderText = "--optional" };
|
||||
var workingDirectoryBox = new TextBox { PlaceholderText = @"{LocalAppData}\Vendor\App" };
|
||||
var descriptionBox = new TextBox { PlaceholderText = "Launch application" };
|
||||
var rows = RemovableRows(_viewModel.Shortcuts);
|
||||
var addButton = new Button { Content = "Add", MinWidth = 72 };
|
||||
addButton.Click += async (_, _) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pathBox.Text) || string.IsNullOrWhiteSpace(targetBox.Text))
|
||||
{
|
||||
await ShowNoticeAsync("Shortcut Entry", "Path and target are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
_viewModel.AddShortcut(
|
||||
pathBox.Text,
|
||||
targetBox.Text,
|
||||
argumentsBox.Text,
|
||||
workingDirectoryBox.Text,
|
||||
descriptionBox.Text);
|
||||
pathBox.Text = string.Empty;
|
||||
targetBox.Text = string.Empty;
|
||||
argumentsBox.Text = string.Empty;
|
||||
workingDirectoryBox.Text = string.Empty;
|
||||
descriptionBox.Text = string.Empty;
|
||||
};
|
||||
var descriptionBox = BoundTextBox(nameof(MainViewModel.ShortcutDescription), "Launch application");
|
||||
|
||||
return Section(
|
||||
"Shortcuts",
|
||||
TwoColumn(Labeled("Path", pathBox), Labeled("Target", targetBox)),
|
||||
TwoColumn(Labeled("Arguments", argumentsBox), Labeled("Working Directory", workingDirectoryBox)),
|
||||
Labeled("Description", descriptionBox),
|
||||
AlignRight(addButton),
|
||||
rows);
|
||||
Labeled("Description", descriptionBox));
|
||||
}
|
||||
|
||||
private FrameworkElement BuildScriptsSection()
|
||||
@@ -466,33 +503,44 @@ internal sealed class MainWindow : Window
|
||||
rows);
|
||||
}
|
||||
|
||||
private FrameworkElement BuildPurgeSection()
|
||||
private async Task ShowInstallerConfigAsync()
|
||||
{
|
||||
var pathBox = new TextBox { PlaceholderText = @"{LocalAppData}\Vendor\App" };
|
||||
var branchBox = new TextBox { PlaceholderText = @"HKCU\Software\VendorApp" };
|
||||
var pathRows = RemovableRows(_viewModel.PurgePaths);
|
||||
var branchRows = RemovableRows(_viewModel.PurgeRegistryBranches);
|
||||
|
||||
var addPathButton = new Button { Content = "Add Path", MinWidth = 96 };
|
||||
addPathButton.Click += (_, _) =>
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
_viewModel.AddPurgePath(pathBox.Text);
|
||||
pathBox.Text = string.Empty;
|
||||
Title = "Installer Config",
|
||||
CloseButtonText = "Close",
|
||||
Width = 1080,
|
||||
MinWidth = 1080,
|
||||
Content = BuildInstallerConfigContent()
|
||||
};
|
||||
|
||||
var addBranchButton = new Button { Content = "Add Branch", MinWidth = 104 };
|
||||
addBranchButton.Click += (_, _) =>
|
||||
if (Content is FrameworkElement root)
|
||||
{
|
||||
_viewModel.AddPurgeRegistryBranch(branchBox.Text);
|
||||
branchBox.Text = string.Empty;
|
||||
dialog.XamlRoot = root.XamlRoot;
|
||||
}
|
||||
|
||||
dialog.Closing += (_, args) =>
|
||||
{
|
||||
if (_isPackaging)
|
||||
{
|
||||
args.Cancel = true;
|
||||
}
|
||||
};
|
||||
|
||||
return Section(
|
||||
"Purge",
|
||||
Labeled("Path", InputRow(pathBox, addPathButton)),
|
||||
pathRows,
|
||||
Labeled("Registry Branch", InputRow(branchBox, addBranchButton)),
|
||||
branchRows);
|
||||
RefreshPackageControls();
|
||||
_ = await dialog.ShowAsync();
|
||||
|
||||
if (_installerDialogMessageText is not null)
|
||||
{
|
||||
_secondaryTextBlocks.Remove(_installerDialogMessageText);
|
||||
}
|
||||
_toolStatusText = null!;
|
||||
_installerDialogMessageText = null!;
|
||||
_covenantSetupPathBox = null!;
|
||||
_browseCovenantSetupButton = null!;
|
||||
_refreshToolButton = null!;
|
||||
_outputDirectoryBox = null!;
|
||||
_chooseOutputButton = null!;
|
||||
}
|
||||
|
||||
private async Task SaveManifestAsync()
|
||||
@@ -505,11 +553,18 @@ internal sealed class MainWindow : Window
|
||||
}
|
||||
|
||||
private async Task<string?> WriteManifestWithPickerAsync(bool showNotice)
|
||||
{
|
||||
return await WriteManifestWithPickerAsync(showNotice, ShowNoticeAsync);
|
||||
}
|
||||
|
||||
private async Task<string?> WriteManifestWithPickerAsync(
|
||||
bool showNotice,
|
||||
Func<string, string, Task> showMessageAsync)
|
||||
{
|
||||
var validation = _viewModel.Validate();
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
await ShowNoticeAsync("Validation", string.Join(Environment.NewLine, validation.Errors));
|
||||
await showMessageAsync("Validation", string.Join(Environment.NewLine, validation.Errors));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -524,7 +579,7 @@ internal sealed class MainWindow : Window
|
||||
await File.WriteAllTextAsync(_lastSavedManifestPath, _viewModel.TomlPreview);
|
||||
if (showNotice)
|
||||
{
|
||||
await ShowNoticeAsync("Manifest Saved", _lastSavedManifestPath);
|
||||
await showMessageAsync("Manifest Saved", _lastSavedManifestPath);
|
||||
}
|
||||
return _lastSavedManifestPath;
|
||||
}
|
||||
@@ -543,7 +598,7 @@ internal sealed class MainWindow : Window
|
||||
|
||||
if (!_viewModel.IsExpectedManifestPath(file.Path))
|
||||
{
|
||||
await ShowNoticeAsync(
|
||||
await showMessageAsync(
|
||||
"Manifest File Name",
|
||||
MainViewModel.ContainsWhitespace(file.Path)
|
||||
? "The manifest path cannot contain spaces. Save the manifest as " + _viewModel.ExpectedManifestFileName + " in a folder path without spaces."
|
||||
@@ -554,11 +609,86 @@ internal sealed class MainWindow : Window
|
||||
await FileIO.WriteTextAsync(file, _viewModel.TomlPreview);
|
||||
if (showNotice)
|
||||
{
|
||||
await ShowNoticeAsync("Manifest Saved", file.Path);
|
||||
await showMessageAsync("Manifest Saved", file.Path);
|
||||
}
|
||||
return file.Path;
|
||||
}
|
||||
|
||||
private async Task BrowseCovenantSetupPathAsync()
|
||||
{
|
||||
var picker = new FileOpenPicker();
|
||||
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
|
||||
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
if (file is not null)
|
||||
{
|
||||
await AcceptCovenantSetupPathAsync(file.Path, showInvalidMessage: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateTypedCovenantSetupPathAsync(bool showInvalidMessage)
|
||||
{
|
||||
if (_covenantSetupPathBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var path = _covenantSetupPathBox.Text.Trim();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
_viewModel.RejectCovenantSetupTool("covenant-setup.exe was not found. Packaging is disabled.");
|
||||
SetInstallerDialogMessage(string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(path, _viewModel.CovenantSetupPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await AcceptCovenantSetupPathAsync(path, showInvalidMessage);
|
||||
}
|
||||
|
||||
private async Task<bool> AcceptCovenantSetupPathAsync(string path, bool showInvalidMessage)
|
||||
{
|
||||
SetInstallerDialogMessage("Checking covenant-setup...");
|
||||
var result = await CovenantSetupToolValidator.ValidateAsync(path, CancellationToken.None);
|
||||
if (result.IsValid && result.Tool is not null)
|
||||
{
|
||||
_viewModel.SetCovenantSetupTool(result.Tool);
|
||||
SyncCovenantSetupPathBox();
|
||||
SetInstallerDialogMessage(string.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
_viewModel.RejectCovenantSetupTool(result.Message);
|
||||
if (showInvalidMessage)
|
||||
{
|
||||
SetInstallerDialogMessage(result.Message + " Run the help check by selecting the real covenant-setup.exe.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SyncCovenantSetupPathBox()
|
||||
{
|
||||
if (_covenantSetupPathBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_syncingCovenantSetupPath = true;
|
||||
try
|
||||
{
|
||||
_covenantSetupPathBox.Text = _viewModel.CovenantSetupPath;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncingCovenantSetupPath = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ChooseOutputDirectoryAsync()
|
||||
{
|
||||
var picker = new FolderPicker();
|
||||
@@ -573,29 +703,30 @@ internal sealed class MainWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateInstallerAsync()
|
||||
private async Task GenerateInstallerAsync(Func<string, string, Task>? showMessageAsync = null)
|
||||
{
|
||||
showMessageAsync ??= ShowNoticeAsync;
|
||||
var tool = _viewModel.CovenantSetupTool;
|
||||
if (tool is null)
|
||||
{
|
||||
await ShowNoticeAsync("Installer EXE", "covenant-setup.exe was not found. Packaging is disabled.");
|
||||
await showMessageAsync("Installer EXE", "covenant-setup.exe was not found. Packaging is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
var validation = _viewModel.Validate();
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
await ShowNoticeAsync("Validation", string.Join(Environment.NewLine, validation.Errors));
|
||||
await showMessageAsync("Validation", string.Join(Environment.NewLine, validation.Errors));
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_viewModel.OutputDirectory))
|
||||
{
|
||||
await ShowNoticeAsync("Installer EXE", "Choose an output directory before packaging.");
|
||||
await showMessageAsync("Installer EXE", "Choose an output directory before packaging.");
|
||||
return;
|
||||
}
|
||||
|
||||
var manifestPath = await WriteManifestWithPickerAsync(showNotice: false);
|
||||
var manifestPath = await WriteManifestWithPickerAsync(showNotice: false, showMessageAsync);
|
||||
if (manifestPath is null)
|
||||
{
|
||||
return;
|
||||
@@ -618,11 +749,11 @@ internal sealed class MainWindow : Window
|
||||
var detail = string.Join(
|
||||
Environment.NewLine + Environment.NewLine,
|
||||
new[] { message, result.Output, result.Error }.Where(part => !string.IsNullOrWhiteSpace(part)));
|
||||
await ShowNoticeAsync("Installer EXE", detail);
|
||||
await showMessageAsync("Installer EXE", detail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ShowNoticeAsync("Installer EXE", "Packaging failed: " + ex.Message);
|
||||
await showMessageAsync("Installer EXE", "Packaging failed: " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -647,7 +778,40 @@ internal sealed class MainWindow : Window
|
||||
var data = new DataPackage();
|
||||
data.SetText(_viewModel.TomlPreview);
|
||||
Clipboard.SetContent(data);
|
||||
await ShowNoticeAsync("TOML Preview", "Copied to clipboard.");
|
||||
await ShowCopyPreviewCopiedAsync();
|
||||
}
|
||||
|
||||
private async Task ShowCopyPreviewCopiedAsync()
|
||||
{
|
||||
var version = ++_copyPreviewFeedbackVersion;
|
||||
var brushes = CreateThemeBrushes();
|
||||
|
||||
_copyPreviewButton.Content = new FontIcon
|
||||
{
|
||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 16,
|
||||
Glyph = "\uE8FB",
|
||||
Foreground = brushes.SuccessText
|
||||
};
|
||||
AutomationProperties.SetName(_copyPreviewButton, "Copied");
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
if (version != _copyPreviewFeedbackVersion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_copyPreviewButton.Content = "Copy";
|
||||
AutomationProperties.SetName(_copyPreviewButton, "Copy TOML preview");
|
||||
}
|
||||
|
||||
private void SetInstallerDialogMessage(string message)
|
||||
{
|
||||
if (_installerDialogMessageText is not null)
|
||||
{
|
||||
_installerDialogMessageText.Text = message;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowNoticeAsync(string title, string message)
|
||||
@@ -760,20 +924,39 @@ internal sealed class MainWindow : Window
|
||||
|
||||
private void RefreshPackageControls()
|
||||
{
|
||||
if (_generateInstallerButton is null)
|
||||
var controlsAvailable = !_isPackaging;
|
||||
if (_covenantSetupPathBox is not null)
|
||||
{
|
||||
return;
|
||||
_covenantSetupPathBox.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_browseCovenantSetupButton is not null)
|
||||
{
|
||||
_browseCovenantSetupButton.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_refreshToolButton is not null)
|
||||
{
|
||||
_refreshToolButton.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_outputDirectoryBox is not null)
|
||||
{
|
||||
_outputDirectoryBox.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_chooseOutputButton is not null)
|
||||
{
|
||||
_chooseOutputButton.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_generateInstallerButton is not null)
|
||||
{
|
||||
_generateInstallerButton.IsEnabled = _viewModel.HasCovenantSetupTool && !_isPackaging;
|
||||
_generateInstallerButton.Content = _isPackaging ? "Building..." : "Save and Build";
|
||||
}
|
||||
|
||||
var toolAvailable = _viewModel.HasCovenantSetupTool && !_isPackaging;
|
||||
_outputDirectoryBox.IsEnabled = toolAvailable;
|
||||
_chooseOutputButton.IsEnabled = toolAvailable;
|
||||
_generateInstallerButton.IsEnabled = _viewModel.CanPackage && !_isPackaging;
|
||||
_generateInstallerButton.Content = _isPackaging ? "Generating..." : "Generate Installer EXE";
|
||||
var brushes = CreateThemeBrushes();
|
||||
_toolStatusText.Foreground = _viewModel.HasCovenantSetupTool
|
||||
? brushes.SuccessText
|
||||
: brushes.ErrorText;
|
||||
if (_toolStatusText is not null)
|
||||
{
|
||||
_toolStatusText.Foreground = _viewModel.HasCovenantSetupTool
|
||||
? brushes.SuccessText
|
||||
: brushes.ErrorText;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyThemeBrushes()
|
||||
@@ -925,7 +1108,11 @@ internal sealed class MainWindow : Window
|
||||
|
||||
private static FrameworkElement Labeled(string label, FrameworkElement control)
|
||||
{
|
||||
var stack = new StackPanel { Spacing = 2 };
|
||||
var stack = new StackPanel
|
||||
{
|
||||
Spacing = 2,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
stack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
@@ -936,6 +1123,47 @@ internal sealed class MainWindow : Window
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static Grid DialogFieldGrid(params (string Label, TextBox TextBox, Button Button)[] fields)
|
||||
{
|
||||
var grid = new Grid
|
||||
{
|
||||
ColumnSpacing = 6,
|
||||
RowSpacing = 2,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
|
||||
for (var index = 0; index < fields.Length; index++)
|
||||
{
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = fields[index].Label,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
FontSize = 12
|
||||
};
|
||||
Grid.SetRow(label, index * 2);
|
||||
Grid.SetColumn(label, 0);
|
||||
grid.Children.Add(label);
|
||||
|
||||
fields[index].TextBox.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
Grid.SetRow(fields[index].TextBox, (index * 2) + 1);
|
||||
Grid.SetColumn(fields[index].TextBox, 0);
|
||||
grid.Children.Add(fields[index].TextBox);
|
||||
|
||||
fields[index].Button.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
fields[index].Button.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
Grid.SetRow(fields[index].Button, (index * 2) + 1);
|
||||
Grid.SetColumn(fields[index].Button, 1);
|
||||
grid.Children.Add(fields[index].Button);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private static Grid TwoColumn(FrameworkElement left, FrameworkElement right)
|
||||
{
|
||||
var grid = new Grid { ColumnSpacing = 6 };
|
||||
@@ -951,8 +1179,11 @@ internal sealed class MainWindow : Window
|
||||
private static Grid InputRow(TextBox textBox, Button button)
|
||||
{
|
||||
var grid = new Grid { ColumnSpacing = 6 };
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(9, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(7, GridUnitType.Star) });
|
||||
grid.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
textBox.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
button.HorizontalAlignment = HorizontalAlignment.Right;
|
||||
Grid.SetColumn(textBox, 0);
|
||||
Grid.SetColumn(button, 1);
|
||||
grid.Children.Add(textBox);
|
||||
|
||||
@@ -51,17 +51,18 @@ The app edits the current manifest schema:
|
||||
- `purge`
|
||||
|
||||
It keeps a live TOML preview, validates required fields locally, warns when a manifest appears to require elevation, and saves a TOML file through the Windows file picker.
|
||||
Shortcut path, target, and working directory values are generated from the Files entries; only the shortcut description is directly edited. Purge paths are generated from file destinations and path-like Registry values, and purge registry branches are generated from the Registry entries.
|
||||
|
||||
## Installer EXE Generation
|
||||
|
||||
The Installer EXE section is enabled only when the app finds `covenant-setup.exe`. It checks:
|
||||
The **Installer Config** button opens installer generation settings. Automatic tool detection checks:
|
||||
|
||||
- next to the authoring app
|
||||
- the current working directory
|
||||
- `target\release` and `target\debug` under nearby repo roots
|
||||
- directories on `PATH`
|
||||
|
||||
When enabled, **Generate Installer EXE** writes the current TOML to the selected `${AppName}-install.toml` location and runs:
|
||||
If the tool is not found automatically, enter or browse to `covenant-setup.exe`; the app runs its `--help` command before accepting a manually selected path. The top-level **Save and Build** button is enabled when an installer engine is detected, writes the current TOML to the selected `${AppName}-install.toml` location, and runs:
|
||||
|
||||
```powershell
|
||||
covenant-setup.exe --json package <AppName>-install.toml --output <output-directory>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,337 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETCoreApp,Version=v10.0/win-x64",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v10.0": {},
|
||||
".NETCoreApp,Version=v10.0/win-x64": {
|
||||
"Covenant.Setup.Authoring/1.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.WindowsAppSDK": "2.0.1",
|
||||
"Microsoft.Web.WebView2.Core.Projection": "1.0.3719.77",
|
||||
"runtimepack.Microsoft.Windows.SDK.NET.Ref": "10.0.19041.57"
|
||||
},
|
||||
"runtime": {
|
||||
"Covenant.Setup.Authoring.dll": {}
|
||||
}
|
||||
},
|
||||
"runtimepack.Microsoft.Windows.SDK.NET.Ref/10.0.19041.57": {
|
||||
"runtime": {
|
||||
"Microsoft.Windows.SDK.NET.dll": {
|
||||
"assemblyVersion": "10.0.19041.38",
|
||||
"fileVersion": "10.0.19041.55"
|
||||
},
|
||||
"WinRT.Runtime.dll": {
|
||||
"assemblyVersion": "2.2.0.0",
|
||||
"fileVersion": "2.2.0.48161"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Web.WebView2/1.0.3719.77": {
|
||||
"native": {
|
||||
"runtimes/win-x64/native/WebView2Loader.dll": {
|
||||
"fileVersion": "1.0.3719.77"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.AI.MachineLearning/2.0.300": {
|
||||
"dependencies": {
|
||||
"System.Numerics.Tensors": "9.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0-windows10.0.17763.0/Microsoft.ML.OnnxRuntime.dll": {
|
||||
"assemblyVersion": "0.0.0.0",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"lib/net8.0-windows10.0.17763.0/Microsoft.Windows.AI.MachineLearning.Projection.dll": {
|
||||
"assemblyVersion": "2.0.300.41724",
|
||||
"fileVersion": "2.0.300.41724"
|
||||
}
|
||||
},
|
||||
"native": {
|
||||
"runtimes/win-x64/native/DirectML.dll": {
|
||||
"fileVersion": "1.15.4.0"
|
||||
},
|
||||
"runtimes/win-x64/native/Microsoft.Windows.AI.MachineLearning.dll": {
|
||||
"fileVersion": "2.0.300.41724"
|
||||
},
|
||||
"runtimes/win-x64/native/onnxruntime.dll": {
|
||||
"fileVersion": "1.24.26.417"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.WindowsAppSDK/2.0.1": {
|
||||
"dependencies": {
|
||||
"Microsoft.WindowsAppSDK.AI": "2.0.185",
|
||||
"Microsoft.WindowsAppSDK.Foundation": "2.0.20",
|
||||
"Microsoft.WindowsAppSDK.InteractiveExperiences": "2.0.12",
|
||||
"Microsoft.WindowsAppSDK.ML": "2.0.300",
|
||||
"Microsoft.WindowsAppSDK.Widgets": "2.0.4",
|
||||
"Microsoft.WindowsAppSDK.WinUI": "2.0.12"
|
||||
}
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.AI/2.0.185": {
|
||||
"dependencies": {
|
||||
"Microsoft.WindowsAppSDK.Foundation": "2.0.20"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Graphics.Imaging.Projection.dll": {
|
||||
"assemblyVersion": "2.0.184.54419",
|
||||
"fileVersion": "2.0.184.54419"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.AI.ContentSafety.Projection.dll": {
|
||||
"assemblyVersion": "2.0.184.54419",
|
||||
"fileVersion": "2.0.184.54419"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.AI.Foundation.Projection.dll": {
|
||||
"assemblyVersion": "2.0.184.54419",
|
||||
"fileVersion": "2.0.184.54419"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.AI.Imaging.Projection.dll": {
|
||||
"assemblyVersion": "2.0.184.54419",
|
||||
"fileVersion": "2.0.184.54419"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.AI.Projection.dll": {
|
||||
"assemblyVersion": "2.0.184.54419",
|
||||
"fileVersion": "2.0.184.54419"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.AI.Text.Projection.dll": {
|
||||
"assemblyVersion": "2.0.184.54419",
|
||||
"fileVersion": "2.0.184.54419"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.Foundation/2.0.20": {
|
||||
"dependencies": {
|
||||
"Microsoft.WindowsAppSDK.InteractiveExperiences": "2.0.12"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Security.Authentication.OAuth.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.AppLifecycle.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.AppNotifications.Builder.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.AppNotifications.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.ApplicationModel.Background.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.ApplicationModel.Resources.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.BadgeNotifications.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.Foundation.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.Management.Deployment.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.Media.Capture.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.PushNotifications.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.Security.AccessControl.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.Storage.Pickers.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.Storage.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.System.Power.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.System.Projection.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.WindowsAppRuntime.Bootstrap.Net.dll": {
|
||||
"assemblyVersion": "2.0.0.0",
|
||||
"fileVersion": "2.0.0.0"
|
||||
}
|
||||
},
|
||||
"native": {
|
||||
"runtimes/win-x64/native/Microsoft.Windows.ApplicationModel.Background.UniversalBGTask.dll": {
|
||||
"fileVersion": "2.0.0.0"
|
||||
},
|
||||
"runtimes/win-x64/native/Microsoft.WindowsAppRuntime.Bootstrap.dll": {
|
||||
"fileVersion": "2.0.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.InteractiveExperiences/2.0.12": {
|
||||
"runtime": {
|
||||
"lib/net6.0-windows10.0.18362.0/Microsoft.InteractiveExperiences.Projection.dll": {
|
||||
"assemblyVersion": "10.0.0.0",
|
||||
"fileVersion": "10.0.27200.1014"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.ML/2.0.300": {
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.AI.MachineLearning": "2.0.300",
|
||||
"Microsoft.WindowsAppSDK.Foundation": "2.0.20"
|
||||
}
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.Widgets/2.0.4": {
|
||||
"runtime": {
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.Windows.Widgets.Projection.dll": {
|
||||
"assemblyVersion": "1.0.0.0",
|
||||
"fileVersion": "1.0.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.WinUI/2.0.12": {
|
||||
"dependencies": {
|
||||
"Microsoft.Web.WebView2": "1.0.3719.77",
|
||||
"Microsoft.WindowsAppSDK.Foundation": "2.0.20",
|
||||
"Microsoft.WindowsAppSDK.InteractiveExperiences": "2.0.12"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0-windows10.0.17763.0/Microsoft.WinUI.dll": {
|
||||
"assemblyVersion": "3.0.0.0",
|
||||
"fileVersion": "3.0.0.2604"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Numerics.Tensors/9.0.0": {
|
||||
"runtime": {
|
||||
"lib/net9.0/System.Numerics.Tensors.dll": {
|
||||
"assemblyVersion": "9.0.0.0",
|
||||
"fileVersion": "9.0.24.52809"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Web.WebView2.Core.Projection/1.0.3719.77": {
|
||||
"runtime": {
|
||||
"Microsoft.Web.WebView2.Core.Projection.dll": {
|
||||
"assemblyVersion": "1.0.3719.77",
|
||||
"fileVersion": "1.0.3719.77"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"Covenant.Setup.Authoring/1.0.0": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
},
|
||||
"runtimepack.Microsoft.Windows.SDK.NET.Ref/10.0.19041.57": {
|
||||
"type": "runtimepack",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
},
|
||||
"Microsoft.Web.WebView2/1.0.3719.77": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-t+ucyKw5NTwMjsUrDF6R9Lk40lpcKQD1/HgyGFxl49tdA4h9dKlsj6FYGEmDRpFNfnTpENuTypMcdbrlkqBdDA==",
|
||||
"path": "microsoft.web.webview2/1.0.3719.77",
|
||||
"hashPath": "microsoft.web.webview2.1.0.3719.77.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Windows.AI.MachineLearning/2.0.300": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-OiJX15N198kG30Lop7LH8WjmBfk2/9iU9it9eLPnkNZ49Yi/vAa4ntONGiGOgv5mb8/fvFHV5trCWSPM+OQhSw==",
|
||||
"path": "microsoft.windows.ai.machinelearning/2.0.300",
|
||||
"hashPath": "microsoft.windows.ai.machinelearning.2.0.300.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.WindowsAppSDK/2.0.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-R+iN0mqIUUppbzpHU178R5malnttTTVBOBIGi1NQVVZAs34bAANM/iLJhjj6uUBgYiPfpvjcLbHT5IZ8kKKaqg==",
|
||||
"path": "microsoft.windowsappsdk/2.0.1",
|
||||
"hashPath": "microsoft.windowsappsdk.2.0.1.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.AI/2.0.185": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-KkWthQS+QuiTQJ8HgSpobUgfJDRUAwps0/1MjYM3wZ/hQzSKr1UoyiYLgk/I8Z2I+/NNphbZ9NsY7DwHKrZJlg==",
|
||||
"path": "microsoft.windowsappsdk.ai/2.0.185",
|
||||
"hashPath": "microsoft.windowsappsdk.ai.2.0.185.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.Foundation/2.0.20": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-PwoRG/eddoi9SOSVPyanei5WAHMyAeVFAeqcovd0V0Ebx6dw7S+ksVVGThAoMjrRI6f93Fa6mTmwXqFHExOtlw==",
|
||||
"path": "microsoft.windowsappsdk.foundation/2.0.20",
|
||||
"hashPath": "microsoft.windowsappsdk.foundation.2.0.20.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.InteractiveExperiences/2.0.12": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-uGNDuIhBHTO1azsa0SCO6/h/0JBVKqoKiJA0K8JSgrlGeC5Lx7g0xppS8aOF/aTnOPSR9BIEHWRvH9JldWTbBg==",
|
||||
"path": "microsoft.windowsappsdk.interactiveexperiences/2.0.12",
|
||||
"hashPath": "microsoft.windowsappsdk.interactiveexperiences.2.0.12.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.ML/2.0.300": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-3/PInpjZozhI+KryPO2b3gZVNyFVOd5qSBhiGW/qFo2VzKQCMc0+cyaCPVm8uhfm3jqgYjQ4g/EqHFA5gJIaoQ==",
|
||||
"path": "microsoft.windowsappsdk.ml/2.0.300",
|
||||
"hashPath": "microsoft.windowsappsdk.ml.2.0.300.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.Widgets/2.0.4": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-gvfIHLmQKBQ+KQKrWb+Oc+eQEZtpKx2zHQDBzFF7Tio+SDkXtaONU4nvHgaqcnkbqNpDPPT/pYIr7UaI51vNEQ==",
|
||||
"path": "microsoft.windowsappsdk.widgets/2.0.4",
|
||||
"hashPath": "microsoft.windowsappsdk.widgets.2.0.4.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.WindowsAppSDK.WinUI/2.0.12": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-qNyajFTgO9xuxKtcs7vLqg7NLSuZK5+EUKeRh6fHQ9Da8hhmymoKci/srxyQwyLkFOXRL7nnuoxovUpzNjlsCg==",
|
||||
"path": "microsoft.windowsappsdk.winui/2.0.12",
|
||||
"hashPath": "microsoft.windowsappsdk.winui.2.0.12.nupkg.sha512"
|
||||
},
|
||||
"System.Numerics.Tensors/9.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-hyJB4UlpAi19Xr9AXzu2NuagKC4lPfHObNMEAA0HmqFz2rX7wKgzeYzO/jM/eBHDhnUGFFEjk5cOoJaxqg5J4A==",
|
||||
"path": "system.numerics.tensors/9.0.0",
|
||||
"hashPath": "system.numerics.tensors.9.0.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Web.WebView2.Core.Projection/1.0.3719.77": {
|
||||
"type": "reference",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net10.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "10.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
|
||||
"CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,844 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" rel="stylesheet" />
|
||||
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>
|
||||
<script src="https://atlas.microsoft.com/sdk/javascript/service/2/atlas-service.min.js"></script>
|
||||
<script>
|
||||
/*
|
||||
This module contains the HtmlMarkerLayer class, which is a layer that renders HTML markers on the map.
|
||||
Source: https://learn.microsoft.com/en-us/samples/azure-samples/azure-maps-html-marker-layer/azure-maps-html-marker-layer-module/
|
||||
|
||||
azure-maps-html-marker-layer Version: 0.0.1
|
||||
MIT License - Copyright (c) Microsoft Corporation.
|
||||
*/
|
||||
|
||||
!function(e, y) {
|
||||
"use strict";
|
||||
var o = function(e, t) {
|
||||
return (o = Object.setPrototypeOf || {
|
||||
__proto__: []
|
||||
}instanceof Array && function(e, t) {
|
||||
e.__proto__ = t
|
||||
}
|
||||
|| function(e, t) {
|
||||
for (var r in t)
|
||||
t.hasOwnProperty(r) && (e[r] = t[r])
|
||||
}
|
||||
)(e, t)
|
||||
};
|
||||
function t(e, t) {
|
||||
function r() {
|
||||
this.constructor = e
|
||||
}
|
||||
o(e, t),
|
||||
e.prototype = null === t ? Object.create(t) : (r.prototype = t.prototype,
|
||||
new r)
|
||||
}
|
||||
var n = function() {
|
||||
return (n = Object.assign || function(e) {
|
||||
for (var t, r = 1, o = arguments.length; r < o; r++)
|
||||
for (var a in t = arguments[r])
|
||||
Object.prototype.hasOwnProperty.call(t, a) && (e[a] = t[a]);
|
||||
return e
|
||||
}
|
||||
).apply(this, arguments)
|
||||
};
|
||||
function a(e, n, s, l) {
|
||||
return new (s = s || Promise)(function(r, t) {
|
||||
function o(e) {
|
||||
try {
|
||||
i(l.next(e))
|
||||
} catch (e) {
|
||||
t(e)
|
||||
}
|
||||
}
|
||||
function a(e) {
|
||||
try {
|
||||
i(l.throw(e))
|
||||
} catch (e) {
|
||||
t(e)
|
||||
}
|
||||
}
|
||||
function i(e) {
|
||||
var t;
|
||||
e.done ? r(e.value) : ((t = e.value)instanceof s ? t : new s(function(e) {
|
||||
e(t)
|
||||
}
|
||||
)).then(o, a)
|
||||
}
|
||||
i((l = l.apply(e, n || [])).next())
|
||||
}
|
||||
)
|
||||
}
|
||||
function g(r, o) {
|
||||
var a, i, n, e, s = {
|
||||
label: 0,
|
||||
sent: function() {
|
||||
if (1 & n[0])
|
||||
throw n[1];
|
||||
return n[1]
|
||||
},
|
||||
trys: [],
|
||||
ops: []
|
||||
};
|
||||
return e = {
|
||||
next: t(0),
|
||||
throw: t(1),
|
||||
return: t(2)
|
||||
},
|
||||
"function" == typeof Symbol && (e[Symbol.iterator] = function() {
|
||||
return this
|
||||
}
|
||||
),
|
||||
e;
|
||||
function t(t) {
|
||||
return function(e) {
|
||||
return function(t) {
|
||||
if (a)
|
||||
throw new TypeError("Generator is already executing.");
|
||||
for (; s; )
|
||||
try {
|
||||
if (a = 1,
|
||||
i && (n = 2 & t[0] ? i.return : t[0] ? i.throw || ((n = i.return) && n.call(i),
|
||||
0) : i.next) && !(n = n.call(i, t[1])).done)
|
||||
return n;
|
||||
switch (i = 0,
|
||||
n && (t = [2 & t[0], n.value]),
|
||||
t[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
n = t;
|
||||
break;
|
||||
case 4:
|
||||
return s.label++,
|
||||
{
|
||||
value: t[1],
|
||||
done: !1
|
||||
};
|
||||
case 5:
|
||||
s.label++,
|
||||
i = t[1],
|
||||
t = [0];
|
||||
continue;
|
||||
case 7:
|
||||
t = s.ops.pop(),
|
||||
s.trys.pop();
|
||||
continue;
|
||||
default:
|
||||
if (!(n = 0 < (n = s.trys).length && n[n.length - 1]) && (6 === t[0] || 2 === t[0])) {
|
||||
s = 0;
|
||||
continue
|
||||
}
|
||||
if (3 === t[0] && (!n || t[1] > n[0] && t[1] < n[3])) {
|
||||
s.label = t[1];
|
||||
break
|
||||
}
|
||||
if (6 === t[0] && s.label < n[1]) {
|
||||
s.label = n[1],
|
||||
n = t;
|
||||
break
|
||||
}
|
||||
if (n && s.label < n[2]) {
|
||||
s.label = n[2],
|
||||
s.ops.push(t);
|
||||
break
|
||||
}
|
||||
n[2] && s.ops.pop(),
|
||||
s.trys.pop();
|
||||
continue
|
||||
}
|
||||
t = o.call(r, s)
|
||||
} catch (e) {
|
||||
t = [6, e],
|
||||
i = 0
|
||||
} finally {
|
||||
a = n = 0
|
||||
}
|
||||
if (5 & t[0])
|
||||
throw t[1];
|
||||
return {
|
||||
value: t[0] ? t[1] : void 0,
|
||||
done: !0
|
||||
}
|
||||
}([t, e])
|
||||
}
|
||||
}
|
||||
}
|
||||
var r = (i.merge = function(e, t) {
|
||||
for (var r = window || global, o = 0, a = e.split("."); o < a.length; o++) {
|
||||
var i = a[o];
|
||||
if (!r[i])
|
||||
return t;
|
||||
r = r[i]
|
||||
}
|
||||
return n(n({}, r), t)
|
||||
}
|
||||
,
|
||||
i);
|
||||
function i() {}
|
||||
var s, l = (t(u, s = y.layer.BubbleLayer),
|
||||
u.prototype.getOptions = function() {
|
||||
return Object.assign({}, this._options)
|
||||
}
|
||||
,
|
||||
u.prototype.setOptions = function(e) {
|
||||
var t = this._options
|
||||
, r = {}
|
||||
, o = !1;
|
||||
e.source && t.source !== e.source && (t.source = e.source,
|
||||
r.source = e.source,
|
||||
o = !0),
|
||||
e.sourceLayer && t.sourceLayer !== e.sourceLayer && (t.sourceLayer = e.sourceLayer,
|
||||
r.sourceLayer = e.sourceLayer,
|
||||
o = !0),
|
||||
e.filter && t.filter !== e.filter && (t.filter = e.filter,
|
||||
r.filter = e.filter,
|
||||
o = !0),
|
||||
"number" == typeof e.minZoom && t.minZoom !== e.minZoom && (t.minZoom = e.minZoom,
|
||||
r.minZoom = e.minZoom),
|
||||
"number" == typeof e.maxZoom && t.maxZoom !== e.maxZoom && (t.maxZoom = e.maxZoom,
|
||||
r.maxZoom = e.maxZoom),
|
||||
void 0 !== e.visible && t.visible !== e.visible && (t.visible = e.visible,
|
||||
r.visible = e.visible),
|
||||
e.markerCallback && t.markerCallback != e.markerCallback && (t.markerCallback = e.markerCallback,
|
||||
o = !0),
|
||||
"boolean" == typeof e.updateWhileMoving && t.updateWhileMoving !== e.updateWhileMoving && (t.updateWhileMoving = e.updateWhileMoving),
|
||||
o ? this._clearCache(!0) : this._updateMarkers(),
|
||||
s.prototype.setOptions.call(this, r)
|
||||
}
|
||||
,
|
||||
u.prototype.update = function() {
|
||||
this._clearCache(!0),
|
||||
this._updateMarkers()
|
||||
}
|
||||
,
|
||||
u.prototype.onAdd = function(e) {
|
||||
var t = this
|
||||
, r = e.events;
|
||||
e && (r.remove("moveend", t._updateMarkers),
|
||||
r.remove("move", t._mapMoved),
|
||||
r.remove("sourcedata", t._sourceUpdated)),
|
||||
(r = (t._map = e).events).add("moveend", t._updateMarkers),
|
||||
r.add("move", t._mapMoved),
|
||||
r.add("sourcedata", t._sourceUpdated),
|
||||
s.prototype.onAdd.call(this, e)
|
||||
}
|
||||
,
|
||||
u.prototype.onRemove = function() {
|
||||
var e = this
|
||||
, t = e._map;
|
||||
if (t) {
|
||||
var r = t.events;
|
||||
r.remove("moveend", e._updateMarkers),
|
||||
r.remove("move", e._mapMoved),
|
||||
r.remove("sourcedata", e._sourceUpdated)
|
||||
}
|
||||
e._clearCache(!1),
|
||||
e._map = null,
|
||||
s.prototype.onRemove.call(this)
|
||||
}
|
||||
,
|
||||
u.prototype._getSourceClass = function() {
|
||||
var e = this.getSource();
|
||||
return "string" == typeof e && null !== this._map ? this._map.sources.getById(e) : e instanceof y.source.Source ? e : null
|
||||
}
|
||||
,
|
||||
u.prototype._clearCache = function(e) {
|
||||
var t = this;
|
||||
if (t._markerCache = {},
|
||||
t._map)
|
||||
for (var r = 0, o = t._markers.length; r < o; r++) {
|
||||
var a = t._markers[r];
|
||||
t._removeEvents(a),
|
||||
a._eventsAttached = !1,
|
||||
t._map.markers.remove(a)
|
||||
}
|
||||
t._markers = [],
|
||||
t._markerIds = [],
|
||||
e && t._updateMarkers()
|
||||
}
|
||||
,
|
||||
u.prototype._getMarker = function(o, a, i) {
|
||||
var n = this
|
||||
, s = n._markerCache
|
||||
, e = n._options;
|
||||
if (o = o || a.join(",") + JSON.stringify(i || {}),
|
||||
s[o])
|
||||
return s[o];
|
||||
var t = e.markerCallback(o, a, i);
|
||||
if (!(t instanceof y.HtmlMarker))
|
||||
return new Promise(function(r) {
|
||||
t.then(function(e) {
|
||||
var t = n._getExtendedMarker(e, o, a, i);
|
||||
t && (s[o] = t,
|
||||
r(t))
|
||||
})
|
||||
}
|
||||
);
|
||||
var r = n._getExtendedMarker(t, o, a, i);
|
||||
return r ? (s[o] = r,
|
||||
Promise.resolve(r)) : null
|
||||
}
|
||||
,
|
||||
u.prototype._getExtendedMarker = function(e, t, r, o) {
|
||||
var a = e;
|
||||
return a ? (a.properties = o,
|
||||
a.id = t,
|
||||
a.setOptions({
|
||||
position: r
|
||||
}),
|
||||
a) : null
|
||||
}
|
||||
,
|
||||
u.prototype._addEvents = function(t) {
|
||||
var r = this;
|
||||
r._supportedEvents.forEach(function(e) {
|
||||
r.map.events.add(e, t, r._wrappedEvent)
|
||||
})
|
||||
}
|
||||
,
|
||||
u.prototype._removeEvents = function(t) {
|
||||
var r = this;
|
||||
r._supportedEvents.forEach(function(e) {
|
||||
r.map.events.remove(e, t, r._wrappedEvent)
|
||||
})
|
||||
}
|
||||
,
|
||||
u);
|
||||
function u(e, t, r) {
|
||||
var o = s.call(this, e, t) || this;
|
||||
return o._options = {
|
||||
minZoom: 0,
|
||||
maxZoom: 24,
|
||||
visible: !0,
|
||||
updateWhileMoving: !1,
|
||||
filter: ["==", ["geometry-type"], "Point"],
|
||||
markerCallback: function(e, t, r) {
|
||||
return r.cluster ? new y.HtmlMarker({
|
||||
position: t,
|
||||
text: r.point_count_abbreviated
|
||||
}) : new y.HtmlMarker({
|
||||
position: t
|
||||
})
|
||||
}
|
||||
},
|
||||
o._markers = [],
|
||||
o._markerIds = [],
|
||||
o._markerCache = {},
|
||||
o._supportedEvents = ["click", "contextmenu", "dblclick", "drag", "dragstart", "dragend", "keydown", "keypress", "keyup", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup"],
|
||||
o._mapMoved = function() {
|
||||
o._options.updateWhileMoving && o._updateMarkers()
|
||||
}
|
||||
,
|
||||
o._sourceUpdated = function(e) {
|
||||
var t = o._getSourceClass();
|
||||
t && t.getId() === e.source.id && (o._timer && clearTimeout(o._timer),
|
||||
o._timer = setTimeout(o._sourceUpdater, 33))
|
||||
}
|
||||
,
|
||||
o._sourceUpdater = function() {
|
||||
o._timer = null,
|
||||
o._clearCache(!0)
|
||||
}
|
||||
,
|
||||
o._updateMarkers = function() {
|
||||
return a(o, void 0, void 0, function() {
|
||||
var t, r, o, a, i, n, s, l, u, c, p, d, m, h, f, v, _, k;
|
||||
return g(this, function(e) {
|
||||
switch (e.label) {
|
||||
case 0:
|
||||
if (r = (t = this)._map,
|
||||
o = t._markers,
|
||||
a = t._options,
|
||||
i = r ? r.getCamera().zoom : void 0,
|
||||
!(a.visible && void 0 !== i && i >= a.minZoom && i <= a.maxZoom))
|
||||
return [3, 5];
|
||||
n = t.getSource(),
|
||||
s = "string" == typeof n ? n : n.getId(),
|
||||
l = r.map.querySourceFeatures(s, {
|
||||
sourceLayer: t.getOptions().sourceLayer,
|
||||
filter: a.filter
|
||||
}),
|
||||
u = [],
|
||||
c = [],
|
||||
v = f = h = m = d = p = void 0,
|
||||
k = 0,
|
||||
_ = l.length,
|
||||
e.label = 1;
|
||||
case 1:
|
||||
return k < _ ? (p = v = null,
|
||||
l[k]instanceof y.Shape ? "Point" === (h = l[k]).getType() && (m = h.getCoordinates(),
|
||||
d = h.getProperties(),
|
||||
p = h.getId()) : "Point" === (f = l[k]).geometry.type && (m = f.geometry.coordinates,
|
||||
(d = f.properties) && d.cluster ? p = "cluster_" + f.properties.cluster_id : f.id && (p = f.id)),
|
||||
m ? [4, t._getMarker(p, m, d)] : [3, 3]) : [3, 4];
|
||||
case 2:
|
||||
(v = e.sent())._eventsAttached || (t._addEvents(v),
|
||||
v._eventsAttached = !0),
|
||||
v && (v.id && c.push(v.id),
|
||||
v.id && -1 !== t._markerIds.indexOf(v.id) || (u.push(v),
|
||||
r.markers.add(v))),
|
||||
e.label = 3;
|
||||
case 3:
|
||||
return k++,
|
||||
[3, 1];
|
||||
case 4:
|
||||
for (k = o.length - 1; 0 <= k; k--)
|
||||
o[k].id && -1 !== c.indexOf(o[k].id) || (r.markers.remove(o[k]),
|
||||
o.splice(k, 1));
|
||||
return t._markers = o.concat(u),
|
||||
t._markerIds = c,
|
||||
[3, 6];
|
||||
case 5:
|
||||
0 < t._markers.length && (r.markers.remove(t._markers),
|
||||
t._markers = []),
|
||||
e.label = 6;
|
||||
case 6:
|
||||
return [2]
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
,
|
||||
o._wrappedEvent = function(e) {
|
||||
o.map.events.invoke(e.type, o, e)
|
||||
}
|
||||
,
|
||||
s.prototype.setOptions.call(o, {
|
||||
color: "transparent",
|
||||
radius: 0,
|
||||
strokeWidth: 0
|
||||
}),
|
||||
o.setOptions(r || {}),
|
||||
o
|
||||
}
|
||||
var b, c = Object.freeze({
|
||||
__proto__: null,
|
||||
HtmlMarkerLayer: l
|
||||
}), p = (t(C, b = y.HtmlMarker),
|
||||
C.prototype.getTotalValue = function() {
|
||||
return this._total
|
||||
}
|
||||
,
|
||||
C.prototype.getSliceValue = function(e) {
|
||||
var t = this._options.values;
|
||||
return 0 <= e && e < t.length ? t[e] : 0
|
||||
}
|
||||
,
|
||||
C.prototype.getSlicePercentage = function(e) {
|
||||
return 0 < this._total ? Math.round(this.getSliceValue(e) / this._total * 1e4) / 100 : 0
|
||||
}
|
||||
,
|
||||
C.prototype.getOptions = function() {
|
||||
return Object.assign({}, b.prototype.getOptions.call(this), this._options)
|
||||
}
|
||||
,
|
||||
C.prototype.setOptions = function(e) {
|
||||
var t = this._options
|
||||
, r = JSON.stringify
|
||||
, o = !1;
|
||||
e.radius && 0 < e.radius && e.radius != t.radius && (t.radius = e.radius,
|
||||
o = !0),
|
||||
0 <= e.innerRadius && e.innerRadius != t.innerRadius && (t.innerRadius = e.innerRadius,
|
||||
o = !0),
|
||||
e.colors && r(e.colors) !== r(t.colors) && (t.colors = e.colors,
|
||||
o = !0),
|
||||
e.fillColor && r(e.fillColor) !== r(t.fillColor) && (t.fillColor = e.fillColor,
|
||||
o = !0),
|
||||
e.strokeColor && e.strokeColor !== t.strokeColor && (t.strokeColor = e.strokeColor,
|
||||
o = !0),
|
||||
0 <= e.strokeWidth && e.strokeWidth != t.strokeWidth && (t.strokeWidth = e.strokeWidth,
|
||||
o = !0),
|
||||
void 0 !== e.tooltipCallback && t.tooltipCallback != e.tooltipCallback && (t.tooltipCallback = e.tooltipCallback,
|
||||
o = !0),
|
||||
e.values && r(e.values) !== r(t.values) && (t.values = e.values,
|
||||
o = !0),
|
||||
void 0 !== e.text && e.text !== t.text && (b.prototype.setOptions.call(this, {
|
||||
text: e.text
|
||||
}),
|
||||
o = !0),
|
||||
void 0 !== e.textClassName && e.textClassName !== t.textClassName && (t.textClassName = e.textClassName,
|
||||
o = !0),
|
||||
o && this._render(),
|
||||
b.prototype.setOptions.call(this, e)
|
||||
}
|
||||
,
|
||||
C.prototype._render = function() {
|
||||
var e = this
|
||||
, t = e._options
|
||||
, r = t.values
|
||||
, o = t.radius
|
||||
, a = 0
|
||||
, i = 0;
|
||||
if (r) {
|
||||
e._total = r.reduce(function(e, t) {
|
||||
return e + t
|
||||
}, 0);
|
||||
for (var n = C._moreColors, s = Math.random, l = Math.round, u = 0; r.length > t.colors.length; )
|
||||
n.length < r.length && n.push("hsl(" + l(360 * s()) + "," + (l(20 * s()) + 70) + "%," + (l(40 * s()) + 30) + "%)"),
|
||||
t.colors.push(n[u]),
|
||||
u++;
|
||||
var c = o + t.strokeWidth
|
||||
, p = ['<svg xmlns="http://www.w3.org/2000/svg" width="' + 2 * c + 'px" height="' + 2 * c + 'px">']
|
||||
, d = ""
|
||||
, m = void 0;
|
||||
if (0 < t.innerRadius && t.innerRadius <= t.radius && (m = "piechart-innercircle-" + l(1e7 * s()),
|
||||
p.push('<defs><mask id="' + m + '"><rect width="100%" height="100%" fill="white"/><circle r="' + t.innerRadius + '" cx="' + c + '" cy="' + c + '" fill="black"/></mask></defs>\n <circle r="' + t.innerRadius + '" cx="' + c + '" cy="' + c + '" style="fill:' + t.fillColor + ";stroke:" + t.strokeColor + ";stroke-width:" + 2 * t.strokeWidth + 'px;"/>')),
|
||||
0 < e._total)
|
||||
for (var h = t.tooltipCallback, f = 2 * Math.PI / e._total, v = 0; v < r.length; v++) {
|
||||
i = f * r[v],
|
||||
h && (d = h(e, v));
|
||||
var _ = v < t.colors.length ? t.colors[v] : n[v];
|
||||
p.push(e._createSlice(c, c, o, a, i, _, d, m)),
|
||||
a += i
|
||||
}
|
||||
var k = e.getOptions().text;
|
||||
k && p.push('<text x="' + c + '" y="' + (c + 7) + '" style="font-size:16px;font-family:arial;fill:#000;font-weight:bold;" class="' + (t.textClassName || "") + '" text-anchor="middle">' + k + "</text>"),
|
||||
p.push("</svg>"),
|
||||
b.prototype.getOptions.call(this).htmlContent.innerHTML = p.join("")
|
||||
}
|
||||
}
|
||||
,
|
||||
C.prototype._createSlice = function(e, t, r, o, a, i, n, s) {
|
||||
var l = this._options
|
||||
, u = Math.PI
|
||||
, c = "";
|
||||
if (s && (c = ' mask="url(#' + s + '"'),
|
||||
2 * u * .99 < a)
|
||||
return '<circle r="' + r + '" cx="' + e + '" cy="' + t + '" style="fill:' + i + ";stroke:" + l.strokeColor + ";stroke-width:" + l.strokeWidth + 'px;"' + c + "><title>" + n + "</title></circle>";
|
||||
var p = Math.sin
|
||||
, d = Math.cos
|
||||
, m = e + r * p(o)
|
||||
, h = t - r * d(o)
|
||||
, f = e + r * p(o + a)
|
||||
, v = t - r * d(o + a)
|
||||
, _ = (l.innerRadius,
|
||||
p(o),
|
||||
l.innerRadius,
|
||||
d(o),
|
||||
l.innerRadius,
|
||||
p(o + a),
|
||||
l.innerRadius,
|
||||
d(o + a),
|
||||
0);
|
||||
return u < a && (_ = 1),
|
||||
'<path d="M' + e + " " + t + " L " + m + " " + h + " A " + r + "," + r + " 0 " + _ + " 1 " + f + " " + v + 'z" style="fill:' + i + ";stroke:" + l.strokeColor + ";stroke-width:" + l.strokeWidth + 'px;"' + c + "><title>" + n + "</title></path>"
|
||||
}
|
||||
,
|
||||
C._moreColors = [],
|
||||
C);
|
||||
function C(e) {
|
||||
var t = b.call(this, e) || this;
|
||||
return t._options = {
|
||||
values: [],
|
||||
radius: 40,
|
||||
colors: ["#d7191c", "#fdae61", "#ffffbf", "#abdda4", "#2b83ba"],
|
||||
fillColor: "transparent",
|
||||
strokeWidth: 0,
|
||||
strokeColor: "#666666",
|
||||
innerRadius: 0
|
||||
},
|
||||
t._total = 0,
|
||||
t.properties = {},
|
||||
b.prototype.setOptions.call(t, {
|
||||
htmlContent: document.createElement("div"),
|
||||
pixelOffset: [0, 0],
|
||||
anchor: "center"
|
||||
}),
|
||||
t.setOptions(e),
|
||||
t
|
||||
}
|
||||
var d = r.merge("atlas.layer", c);
|
||||
e.PieChartMarker = p,
|
||||
e.layer = d
|
||||
}(this.atlas = this.atlas || {}, atlas);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-family: segoeui;
|
||||
}
|
||||
|
||||
#mapContainer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div id="mapContainer"></div>
|
||||
|
||||
<script>
|
||||
var map;
|
||||
var symbolLayers = [];
|
||||
var layersData = {};
|
||||
var pointsData = {};
|
||||
var dataSource;
|
||||
var id = 1;
|
||||
var controls = [];
|
||||
|
||||
function initializeMap(longitude, latitude, mapServiceToken) {
|
||||
try {
|
||||
//Initialize a map instance.
|
||||
map = new atlas.Map('mapContainer', {
|
||||
center: [longitude, latitude],
|
||||
zoom: 3,
|
||||
view: 'Auto',
|
||||
|
||||
//Add authentication details for connecting to Azure Maps.
|
||||
// Note: an invalid token doesn't seem to throw any errors.
|
||||
authOptions: {
|
||||
authType: 'subscriptionKey',
|
||||
subscriptionKey: mapServiceToken
|
||||
}
|
||||
});
|
||||
|
||||
map.events.add('ready', function () {
|
||||
dataSource = new atlas.source.DataSource();
|
||||
map.sources.add(dataSource);
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function addSymbolLayer() {
|
||||
try {
|
||||
var layerSource = new atlas.source.DataSource();
|
||||
var newLayer = new atlas.layer.HtmlMarkerLayer(layerSource, null, {
|
||||
markerCallback: (id, position, properties) => {
|
||||
const pin = new atlas.HtmlMarker({
|
||||
position: position,
|
||||
id: id
|
||||
});
|
||||
pin.getElement().setAttribute('tabindex', 0);
|
||||
return pin;
|
||||
}
|
||||
});
|
||||
|
||||
layersData[newLayer.getId()] = layerSource;
|
||||
pointsData[newLayer.getId()] = {};
|
||||
symbolLayers.push(newLayer);
|
||||
|
||||
map.events.add('ready', function () {
|
||||
map.sources.add(layerSource);
|
||||
map.layers.add(newLayer);
|
||||
map.events.add('click', newLayer, sendPushpinClickInfo);
|
||||
});
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
return newLayer.getId();
|
||||
}
|
||||
|
||||
function addPoint(longitude, latitude, layerId) {
|
||||
var point = new atlas.data.Feature(new atlas.data.Point([longitude, latitude]), {name: 'MapElement'}, id);
|
||||
id++;
|
||||
try {
|
||||
map.events.add('ready', function () {
|
||||
layersData[layerId].add([point]);
|
||||
pointsData[layerId][point.id] = point;
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
|
||||
return point.id;
|
||||
}
|
||||
|
||||
function updatePoint(longitude, latitude, pointId, layerId) {
|
||||
try {
|
||||
map.events.add('ready', function () {
|
||||
pointsData[layerId][pointId].setCoordinates([longitude, latitude]);
|
||||
|
||||
logSymbolLayers();
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function removePoint(pointId, layerId) {
|
||||
try {
|
||||
map.events.add('ready', function() {
|
||||
layersData[layerId].remove(pointsData[layerId][pointId]);
|
||||
delete pointsData[layerId][pointId];
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function clearLayer(layerId) {
|
||||
try {
|
||||
map.events.add( 'ready', function() {
|
||||
layersData[layerId].clear();
|
||||
pointsData[layerId] = {};
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function clearMap() {
|
||||
try {
|
||||
map.events.add( 'ready', function() {
|
||||
symbolLayers = [];
|
||||
layersData = {};
|
||||
pointsData = {};
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function interactiveControlsVisible(enable) {
|
||||
if (enable) {
|
||||
controls = [
|
||||
new atlas.control.ZoomControl(),
|
||||
new atlas.control.CompassControl(),
|
||||
new atlas.control.PitchControl(),
|
||||
new atlas.control.StyleControl()
|
||||
]
|
||||
map.controls.add(controls, {
|
||||
position: "top-right"
|
||||
});
|
||||
} else {
|
||||
map.controls.remove(controls);
|
||||
controls = [];
|
||||
}
|
||||
}
|
||||
|
||||
function updateCenter(latitude, longitude) {
|
||||
try {
|
||||
map.setCamera({
|
||||
center: [longitude, latitude]
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateZoom(level) {
|
||||
try {
|
||||
map.setCamera({
|
||||
zoom: level
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateMapServiceToken(mapServiceToken) {
|
||||
try {
|
||||
clearMap();
|
||||
var location = map.getCamera().center;
|
||||
longitude = location[0];
|
||||
latitude = location[1];
|
||||
// create a new map with the new API key
|
||||
initializeMap(longitude, latitude, mapServiceToken);
|
||||
} catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function sendPushpinClickInfo(e) {
|
||||
try {
|
||||
const latitude = e.target.options.position[0];
|
||||
const longitude = e.target.options.position[1];
|
||||
const pointClicked = e.target.id || 'Unknown';
|
||||
|
||||
const coordinate = {
|
||||
latitude,
|
||||
longitude,
|
||||
};
|
||||
|
||||
const message = {
|
||||
type: 'pushpinClickEvent',
|
||||
coordinate,
|
||||
layer: 0,
|
||||
point: pointClicked,
|
||||
text: 'Pushpin clicked!',
|
||||
};
|
||||
|
||||
console.log('Sending message:', message);
|
||||
window.chrome.webview.postMessage(message);
|
||||
} catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function getMapLayers() {
|
||||
try {
|
||||
layers = map.layers.getLayers();
|
||||
console.log("Number of layers: " + layers.length + "\n");
|
||||
// print out the name and type of each layer
|
||||
for (var i = 0; i < layers.length; i++) {
|
||||
if (layers[i] instanceof atlas.layer.SymbolLayer) {
|
||||
console.log(layers[i].getId() + " is a SymbolLayer\n");
|
||||
console.log("Source: " + layers[i].getSource() + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function logSymbolLayers() {
|
||||
try {
|
||||
// print out each symbolLayer in symbolLayers by id
|
||||
console.log("Number of symbol layers: " + symbolLayers.length + "\n");
|
||||
for (var i = 0; i < symbolLayers.length; i++) {
|
||||
console.log("Id: " + symbolLayers[i].getId() + "\n");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
sendErrorInfo(error);
|
||||
}
|
||||
}
|
||||
|
||||
function getSymbolLayers() {
|
||||
return symbolLayers;
|
||||
}
|
||||
|
||||
function sendErrorInfo(e) {
|
||||
console.log(e);
|
||||
const coordinate = {
|
||||
"latitude": 0,
|
||||
"longitude": 0,
|
||||
}
|
||||
|
||||
const message = {
|
||||
"type": "javascriptError",
|
||||
"coordinate": coordinate,
|
||||
"text": e.stack
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user