Rilascia strumenti avanzati e archivio 0.3.0
This commit is contained in:
1 parent
b19afbce91
commit
620a4bec26
8 files changed
+1351
-33
No files matched your search
@@ -35,6 +35,8 @@ public sealed class MapExportService
|
||||
{
|
||||
"kml" => "kml",
|
||||
"kmz" => "kmz",
|
||||
"gpx" => "gpx",
|
||||
"pdf" => "pdf",
|
||||
_ => "geojson"
|
||||
};
|
||||
var baseName = NormalizeFileName(requestedName);
|
||||
@@ -49,6 +51,18 @@ public sealed class MapExportService
|
||||
return new(path, "application/geo+json", featureCount, Path.GetFileName(path));
|
||||
}
|
||||
|
||||
if (format == "gpx")
|
||||
{
|
||||
await File.WriteAllTextAsync(path, BuildGpx(features, baseName), new UTF8Encoding(false), cancellationToken);
|
||||
return new(path, "application/gpx+xml", featureCount, Path.GetFileName(path));
|
||||
}
|
||||
|
||||
if (format == "pdf")
|
||||
{
|
||||
await File.WriteAllBytesAsync(path, BuildPdfReport(features, baseName), cancellationToken);
|
||||
return new(path, "application/pdf", featureCount, Path.GetFileName(path));
|
||||
}
|
||||
|
||||
var kml = BuildKml(features, baseName);
|
||||
if (format == "kml")
|
||||
{
|
||||
@@ -67,6 +81,108 @@ public sealed class MapExportService
|
||||
return new(path, "application/vnd.google-earth.kmz", featureCount, Path.GetFileName(path));
|
||||
}
|
||||
|
||||
private static string BuildGpx(JsonElement features, string name)
|
||||
{
|
||||
var root = new XElement("gpx",
|
||||
new XAttribute("version", "1.1"),
|
||||
new XAttribute("creator", "LME Mappe Catastali 0.3.0"),
|
||||
new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"),
|
||||
new XElement("metadata", new XElement("name", name)));
|
||||
foreach (var feature in features.EnumerateArray())
|
||||
{
|
||||
if (!feature.TryGetProperty("geometry", out var geometry) || geometry.ValueKind != JsonValueKind.Object) continue;
|
||||
var properties = feature.TryGetProperty("properties", out var value) ? value : default;
|
||||
var featureName = FeatureName(properties, GetString(properties, "_lmeCategory") ?? "drawing");
|
||||
var type = geometry.GetProperty("type").GetString();
|
||||
var coordinates = geometry.GetProperty("coordinates");
|
||||
if (type == "Point")
|
||||
{
|
||||
root.Add(GpxPoint("wpt", coordinates, featureName));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var line in EnumerateGpxLines(type, coordinates))
|
||||
{
|
||||
root.Add(new XElement("trk", new XElement("name", featureName),
|
||||
new XElement("trkseg", line.Select(point => GpxPoint("trkpt", point, null)))));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new XDocument(new XDeclaration("1.0", "utf-8", null), root).ToString();
|
||||
}
|
||||
|
||||
private static XElement GpxPoint(string element, JsonElement coordinate, string? name)
|
||||
{
|
||||
var values = coordinate.EnumerateArray().ToArray();
|
||||
var point = new XElement(element,
|
||||
new XAttribute("lat", values[1].GetDouble().ToString("G17", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("lon", values[0].GetDouble().ToString("G17", CultureInfo.InvariantCulture)));
|
||||
if (!string.IsNullOrWhiteSpace(name)) point.Add(new XElement("name", name));
|
||||
return point;
|
||||
}
|
||||
|
||||
private static IEnumerable<JsonElement[]> EnumerateGpxLines(string? type, JsonElement coordinates)
|
||||
{
|
||||
if (type == "LineString") yield return coordinates.EnumerateArray().ToArray();
|
||||
else if (type == "Polygon")
|
||||
foreach (var ring in coordinates.EnumerateArray()) yield return ring.EnumerateArray().ToArray();
|
||||
else if (type == "MultiLineString")
|
||||
foreach (var line in coordinates.EnumerateArray()) yield return line.EnumerateArray().ToArray();
|
||||
else if (type == "MultiPolygon")
|
||||
foreach (var polygon in coordinates.EnumerateArray())
|
||||
foreach (var ring in polygon.EnumerateArray()) yield return ring.EnumerateArray().ToArray();
|
||||
}
|
||||
|
||||
private static byte[] BuildPdfReport(JsonElement features, string title)
|
||||
{
|
||||
var lines = new List<string>
|
||||
{
|
||||
title,
|
||||
$"Rapporto LME Mappe Catastali - {DateTime.Now:dd/MM/yyyy HH:mm}",
|
||||
$"Oggetti: {features.GetArrayLength()}",
|
||||
""
|
||||
};
|
||||
var index = 1;
|
||||
foreach (var feature in features.EnumerateArray())
|
||||
{
|
||||
var properties = feature.TryGetProperty("properties", out var value) ? value : default;
|
||||
var category = GetString(properties, "_lmeCategory") ?? "drawing";
|
||||
var geometry = feature.TryGetProperty("geometry", out var geo) ? geo.GetProperty("type").GetString() : "-";
|
||||
lines.Add($"{index++}. {FeatureName(properties, category)} [{category}, {geometry}]");
|
||||
}
|
||||
var content = new StringBuilder("BT /F1 16 Tf 48 790 Td ");
|
||||
foreach (var line in lines.Take(42))
|
||||
{
|
||||
var safe = line.Replace("\\", "\\\\").Replace("(", "\\(").Replace(")", "\\)")
|
||||
.Select(character => character <= 255 ? character : '?').ToArray();
|
||||
content.Append('(').Append(safe).Append(") Tj 0 -18 Td ");
|
||||
}
|
||||
content.Append("ET");
|
||||
var stream = Encoding.Latin1.GetBytes(content.ToString());
|
||||
var objects = new[]
|
||||
{
|
||||
"<< /Type /Catalog /Pages 2 0 R >>",
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
|
||||
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>",
|
||||
$"<< /Length {stream.Length} >>\nstream\n{Encoding.Latin1.GetString(stream)}\nendstream",
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>"
|
||||
};
|
||||
using var output = new MemoryStream();
|
||||
using var writer = new StreamWriter(output, Encoding.Latin1, leaveOpen: true) { NewLine = "\n" };
|
||||
writer.Write("%PDF-1.4\n"); writer.Flush();
|
||||
var offsets = new List<long> { 0 };
|
||||
for (var i = 0; i < objects.Length; i++)
|
||||
{
|
||||
offsets.Add(output.Position); writer.Write($"{i + 1} 0 obj\n{objects[i]}\nendobj\n"); writer.Flush();
|
||||
}
|
||||
var xref = output.Position;
|
||||
writer.Write($"xref\n0 {objects.Length + 1}\n0000000000 65535 f \n");
|
||||
foreach (var offset in offsets.Skip(1)) writer.Write($"{offset:0000000000} 00000 n \n");
|
||||
writer.Write($"trailer << /Size {objects.Length + 1} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF");
|
||||
writer.Flush();
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
private static string BuildKml(JsonElement features, string documentName)
|
||||
{
|
||||
var document = new XElement(Kml + "Document",
|
||||
@@ -97,6 +213,11 @@ public sealed class MapExportService
|
||||
var placemark = new XElement(Kml + "Placemark",
|
||||
new XElement(Kml + "name", FeatureName(properties, category)),
|
||||
new XElement(Kml + "styleUrl", $"#{NormalizeStyleId(category)}"));
|
||||
var featureStyle = BuildFeatureStyle(properties);
|
||||
if (featureStyle is not null)
|
||||
{
|
||||
placemark.Add(featureStyle);
|
||||
}
|
||||
|
||||
var extendedData = BuildExtendedData(properties);
|
||||
if (extendedData is not null)
|
||||
@@ -142,6 +263,37 @@ public sealed class MapExportService
|
||||
new XElement(Kml + "width", 3)),
|
||||
new XElement(Kml + "PolyStyle", new XElement(Kml + "color", fillColor)));
|
||||
|
||||
private static XElement? BuildFeatureStyle(JsonElement properties)
|
||||
{
|
||||
var color = GetString(properties, "_lmeColor");
|
||||
var fillColor = GetString(properties, "_lmeFillColor") ?? color;
|
||||
if (string.IsNullOrWhiteSpace(color) && string.IsNullOrWhiteSpace(fillColor)) return null;
|
||||
var weight = GetNumber(properties, "_lmeWeight", 3);
|
||||
var opacity = GetNumber(properties, "_lmeOpacity", 1);
|
||||
var fillOpacity = GetNumber(properties, "_lmeFillOpacity", 0.2);
|
||||
return new XElement(Kml + "Style",
|
||||
new XElement(Kml + "LineStyle",
|
||||
new XElement(Kml + "color", ToKmlColor(color ?? "#f97316", opacity)),
|
||||
new XElement(Kml + "width", weight.ToString("0.##", CultureInfo.InvariantCulture))),
|
||||
new XElement(Kml + "PolyStyle",
|
||||
new XElement(Kml + "color", ToKmlColor(fillColor ?? "#fb923c", fillOpacity))));
|
||||
}
|
||||
|
||||
private static double GetNumber(JsonElement properties, string name, double fallback) =>
|
||||
properties.ValueKind == JsonValueKind.Object
|
||||
&& properties.TryGetProperty(name, out var value)
|
||||
&& value.ValueKind == JsonValueKind.Number
|
||||
? value.GetDouble()
|
||||
: fallback;
|
||||
|
||||
private static string ToKmlColor(string html, double opacity)
|
||||
{
|
||||
var value = html.TrimStart('#');
|
||||
if (value.Length != 6) value = "f97316";
|
||||
var alpha = (int)Math.Round(Math.Clamp(opacity, 0, 1) * 255);
|
||||
return $"{alpha:x2}{value[4..6]}{value[2..4]}{value[0..2]}";
|
||||
}
|
||||
|
||||
private static XElement? BuildExtendedData(JsonElement properties)
|
||||
{
|
||||
if (properties.ValueKind != JsonValueKind.Object)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace LMEMappeCatastaliApp.Services;
|
||||
|
||||
@@ -13,8 +14,9 @@ public sealed class ProjectStorageService
|
||||
};
|
||||
|
||||
private string ProjectsDirectory => Path.Combine(FileSystem.AppDataDirectory, "Projects");
|
||||
private string HistoryDirectory => Path.Combine(ProjectsDirectory, ".history");
|
||||
|
||||
public async Task<IReadOnlyList<MapProjectSummary>> GetProjectsAsync()
|
||||
public async Task<IReadOnlyList<MapProjectSummary>> GetProjectsAsync(bool includeArchived = true)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
@@ -22,6 +24,7 @@ public sealed class ProjectStorageService
|
||||
var files = await ReadProjectFilesAsync();
|
||||
return files
|
||||
.Select(item => item.Document.ToSummary())
|
||||
.Where(item => includeArchived || !item.IsArchived)
|
||||
.OrderByDescending(item => item.UpdatedAt)
|
||||
.ThenBy(item => item.Name, StringComparer.CurrentCultureIgnoreCase)
|
||||
.ToArray();
|
||||
@@ -56,14 +59,23 @@ public sealed class ProjectStorageService
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var id = existing?.Document.Id ?? Guid.NewGuid().ToString("N");
|
||||
if (existing is not null)
|
||||
{
|
||||
Directory.CreateDirectory(HistoryDirectory);
|
||||
var revisionName = $"{id}--{existing.Document.UpdatedAt:yyyyMMddHHmmssfff}{ProjectExtension}";
|
||||
File.Copy(existing.Path, Path.Combine(HistoryDirectory, revisionName), true);
|
||||
PruneHistory(id, 15);
|
||||
}
|
||||
var document = new MapProjectDocument
|
||||
{
|
||||
SchemaVersion = 1,
|
||||
SchemaVersion = 2,
|
||||
Id = id,
|
||||
Name = normalizedName,
|
||||
CreatedAt = existing?.Document.CreatedAt ?? now,
|
||||
UpdatedAt = now,
|
||||
IncludesCadastralParcels = includesCadastralParcels,
|
||||
Folder = existing?.Document.Folder ?? string.Empty,
|
||||
IsArchived = existing?.Document.IsArchived ?? false,
|
||||
Map = snapshotDocument.RootElement.Clone()
|
||||
};
|
||||
|
||||
@@ -93,6 +105,178 @@ public sealed class ProjectStorageService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MapProjectSummary> DuplicateAsync(string projectId)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
var files = await ReadProjectFilesAsync();
|
||||
var source = files.FirstOrDefault(item => item.Document.Id == projectId)
|
||||
?? throw new FileNotFoundException("Il progetto selezionato non esiste piu'.");
|
||||
var usedNames = files.Select(item => item.Document.Name).ToHashSet(StringComparer.CurrentCultureIgnoreCase);
|
||||
var baseName = $"{source.Document.Name} - copia";
|
||||
var name = baseName;
|
||||
for (var index = 2; usedNames.Contains(name); index++)
|
||||
{
|
||||
name = $"{baseName} {index}";
|
||||
}
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var id = Guid.NewGuid().ToString("N");
|
||||
var copy = new MapProjectDocument
|
||||
{
|
||||
SchemaVersion = 2,
|
||||
Id = id,
|
||||
Name = name,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now,
|
||||
IncludesCadastralParcels = source.Document.IncludesCadastralParcels,
|
||||
Folder = source.Document.Folder,
|
||||
Map = source.Document.Map.Clone()
|
||||
};
|
||||
var path = Path.Combine(ProjectsDirectory, BuildFileName(name, id));
|
||||
await File.WriteAllTextAsync(path, JsonSerializer.Serialize(copy, _jsonOptions), new UTF8Encoding(false));
|
||||
return copy.ToSummary();
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
public async Task UpdateOrganizationAsync(string projectId, string? folder = null, bool? isArchived = null)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
var project = (await ReadProjectFilesAsync()).FirstOrDefault(item => item.Document.Id == projectId)
|
||||
?? throw new FileNotFoundException("Il progetto selezionato non esiste piu'.");
|
||||
project.Document.Folder = NormalizeFolder(folder ?? project.Document.Folder);
|
||||
project.Document.IsArchived = isArchived ?? project.Document.IsArchived;
|
||||
project.Document.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
await File.WriteAllTextAsync(project.Path, JsonSerializer.Serialize(project.Document, _jsonOptions), new UTF8Encoding(false));
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<MapProjectVersion>> GetVersionsAsync(string projectId)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(HistoryDirectory);
|
||||
var versions = new List<MapProjectVersion>();
|
||||
foreach (var path in Directory.EnumerateFiles(HistoryDirectory, $"{projectId}--*{ProjectExtension}"))
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var stream = File.OpenRead(path);
|
||||
var document = await JsonSerializer.DeserializeAsync<MapProjectDocument>(stream, _jsonOptions);
|
||||
if (document is not null)
|
||||
{
|
||||
versions.Add(new(Path.GetFileName(path), document.UpdatedAt));
|
||||
}
|
||||
}
|
||||
catch (JsonException) { }
|
||||
catch (IOException) { }
|
||||
}
|
||||
return versions.OrderByDescending(item => item.UpdatedAt).ToArray();
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
public async Task<string> LoadVersionAsync(string projectId, string versionId)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!versionId.StartsWith($"{projectId}--", StringComparison.Ordinal)
|
||||
|| Path.GetFileName(versionId) != versionId)
|
||||
{
|
||||
throw new InvalidDataException("Revisione non valida.");
|
||||
}
|
||||
var path = Path.Combine(HistoryDirectory, versionId);
|
||||
await using var stream = File.OpenRead(path);
|
||||
var document = await JsonSerializer.DeserializeAsync<MapProjectDocument>(stream, _jsonOptions)
|
||||
?? throw new InvalidDataException("Revisione non leggibile.");
|
||||
return document.Map.GetRawText();
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
public async Task<string> CreateBackupAsync()
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(ProjectsDirectory);
|
||||
var directory = Path.Combine(FileSystem.CacheDirectory, "Backups");
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, $"LMEMappeCatastali-backup-{DateTime.Now:yyyyMMdd-HHmmss}.lmebackup.zip");
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
ZipFile.CreateFromDirectory(ProjectsDirectory, path, CompressionLevel.Optimal, false);
|
||||
return path;
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
public async Task<int> RestoreBackupAsync(string backupPath)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(ProjectsDirectory);
|
||||
using var archive = ZipFile.OpenRead(backupPath);
|
||||
var restored = 0;
|
||||
foreach (var entry in archive.Entries.Where(item => item.FullName.EndsWith(ProjectExtension, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var fileName = Path.GetFileName(entry.FullName);
|
||||
if (string.IsNullOrWhiteSpace(fileName)) continue;
|
||||
var targetDirectory = entry.FullName.Contains(".history/", StringComparison.OrdinalIgnoreCase)
|
||||
|| entry.FullName.Contains(".history\\", StringComparison.OrdinalIgnoreCase)
|
||||
? HistoryDirectory : ProjectsDirectory;
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
entry.ExtractToFile(Path.Combine(targetDirectory, fileName), true);
|
||||
if (targetDirectory == ProjectsDirectory) restored++;
|
||||
}
|
||||
return restored;
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
public async Task<string> CreateProjectPackageAsync(string projectId)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
var project = (await ReadProjectFilesAsync()).FirstOrDefault(item => item.Document.Id == projectId)
|
||||
?? throw new FileNotFoundException("Il progetto selezionato non esiste piu'.");
|
||||
var directory = Path.Combine(FileSystem.CacheDirectory, "Projects");
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, Path.GetFileName(project.Path));
|
||||
File.Copy(project.Path, path, true);
|
||||
return path;
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
public async Task<MapProjectSummary> ImportProjectAsync(string path)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
await using var stream = File.OpenRead(path);
|
||||
var source = await JsonSerializer.DeserializeAsync<MapProjectDocument>(stream, _jsonOptions)
|
||||
?? throw new InvalidDataException("Progetto non leggibile.");
|
||||
if (source.Map.ValueKind != JsonValueKind.Object) throw new InvalidDataException("Mappa del progetto non valida.");
|
||||
source.Id = Guid.NewGuid().ToString("N");
|
||||
source.Name = NormalizeName(source.Name);
|
||||
source.CreatedAt = source.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
source.IsArchived = false;
|
||||
Directory.CreateDirectory(ProjectsDirectory);
|
||||
var target = Path.Combine(ProjectsDirectory, BuildFileName(source.Name, source.Id));
|
||||
await File.WriteAllTextAsync(target, JsonSerializer.Serialize(source, _jsonOptions), new UTF8Encoding(false));
|
||||
return source.ToSummary();
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
public async Task<string> LoadAsync(string projectId)
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
@@ -118,6 +302,13 @@ public sealed class ProjectStorageService
|
||||
.FirstOrDefault(item => item.Document.Id == projectId)
|
||||
?? throw new FileNotFoundException("Il progetto selezionato non esiste piu'.");
|
||||
File.Delete(project.Path);
|
||||
if (Directory.Exists(HistoryDirectory))
|
||||
{
|
||||
foreach (var path in Directory.EnumerateFiles(HistoryDirectory, $"{projectId}--*{ProjectExtension}"))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -179,19 +370,36 @@ public sealed class ProjectStorageService
|
||||
return $"{safeName}--{id[..8]}{ProjectExtension}";
|
||||
}
|
||||
|
||||
private void PruneHistory(string projectId, int keep)
|
||||
{
|
||||
foreach (var path in Directory.EnumerateFiles(HistoryDirectory, $"{projectId}--*{ProjectExtension}")
|
||||
.OrderByDescending(File.GetLastWriteTimeUtc).Skip(keep))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeFolder(string value)
|
||||
{
|
||||
var normalized = string.Join(' ', (value ?? string.Empty).Split([' ', '\t', '\r', '\n'], StringSplitOptions.RemoveEmptyEntries));
|
||||
return normalized[..Math.Min(normalized.Length, 60)];
|
||||
}
|
||||
|
||||
private sealed record ProjectFileEntry(string Path, MapProjectDocument Document);
|
||||
|
||||
private sealed class MapProjectDocument
|
||||
{
|
||||
public int SchemaVersion { get; init; }
|
||||
public string Id { get; init; } = string.Empty;
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
public bool IncludesCadastralParcels { get; init; }
|
||||
public JsonElement Map { get; init; }
|
||||
public int SchemaVersion { get; set; }
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
public bool IncludesCadastralParcels { get; set; }
|
||||
public string Folder { get; set; } = string.Empty;
|
||||
public bool IsArchived { get; set; }
|
||||
public JsonElement Map { get; set; }
|
||||
|
||||
public MapProjectSummary ToSummary() => new(Id, Name, UpdatedAt, IncludesCadastralParcels);
|
||||
public MapProjectSummary ToSummary() => new(Id, Name, UpdatedAt, IncludesCadastralParcels, Folder, IsArchived);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,4 +407,8 @@ public sealed record MapProjectSummary(
|
||||
string Id,
|
||||
string Name,
|
||||
DateTimeOffset UpdatedAt,
|
||||
bool IncludesCadastralParcels);
|
||||
bool IncludesCadastralParcels,
|
||||
string Folder,
|
||||
bool IsArchived);
|
||||
|
||||
public sealed record MapProjectVersion(string Id, DateTimeOffset UpdatedAt);
|
||||
Reference in new issue
Block a user