diff --git a/CMakeLists.txt b/CMakeLists.txt index e2083b78..ff48db67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ message("Building for ${CMAKE_SYSTEM_NAME}.") if (NOT TARGET mapget) FetchContent_Declare(mapget GIT_REPOSITORY "https://github.com/Klebert-Engineering/mapget" - GIT_TAG "v2024.5.0" + GIT_TAG "legal-info-entry" GIT_SHALLOW ON) FetchContent_MakeAvailable(mapget) endif() diff --git a/erdblick_app/app/app.component.ts b/erdblick_app/app/app.component.ts index 89c07dbe..e2b8f044 100644 --- a/erdblick_app/app/app.component.ts +++ b/erdblick_app/app/app.component.ts @@ -18,6 +18,10 @@ import {filter} from "rxjs"; + +
{{ title }} {{ version }}
@@ -36,19 +40,25 @@ export class AppComponent { title: string = "erdblick"; version: string = ""; + copyright: string = ""; constructor(private httpClient: HttpClient, private router: Router, private activatedRoute: ActivatedRoute, public mapService: MapService, - public styleService: StyleService, - public jumpToTargetService: JumpTargetService, public parametersService: ParametersService) { this.httpClient.get('./bundle/VERSION', {responseType: 'text'}).subscribe( data => { this.version = data.toString(); }); this.init(); + this.mapService.legalInformationUpdated.subscribe(_ => { + this.copyright = ""; + let firstSet: Set | undefined = this.mapService.legalInformationPerMap.values().next().value; + if (firstSet !== undefined && firstSet.size) { + this.copyright = '© '.concat(firstSet.values().next().value as string).slice(0, 14).concat('…'); + } + }); } init() { @@ -83,4 +93,8 @@ export class AppComponent { replaceUrl: replaceUrl }); } + + openLegalInfo() { + this.parametersService.legalInfoDialogVisible = true; + } } diff --git a/erdblick_app/app/app.module.ts b/erdblick_app/app/app.module.ts index 26c19551..b66fb93c 100644 --- a/erdblick_app/app/app.module.ts +++ b/erdblick_app/app/app.module.ts @@ -76,6 +76,7 @@ import {StatsDialogComponent} from "./stats.component"; import {SourceDataLayerSelectionDialogComponent} from "./sourcedataselection.dialog.component"; import {ContextMenuModule} from "primeng/contextmenu"; import {RightClickMenuService} from "./rightclickmenu.service"; +import {LegalInfoDialogComponent} from "./legalinfo.component"; export function initializeServices(styleService: StyleService, mapService: MapService, coordService: CoordinatesService) { return async () => { @@ -152,7 +153,8 @@ export function typeValidationMessage({ schemaType }: any) { HighlightSearch, TreeTableFilterPatchDirective, StatsDialogComponent, - SourceDataLayerSelectionDialogComponent + SourceDataLayerSelectionDialogComponent, + LegalInfoDialogComponent ], bootstrap: [ AppComponent diff --git a/erdblick_app/app/features.model.ts b/erdblick_app/app/features.model.ts index 318c51a7..8268933b 100644 --- a/erdblick_app/app/features.model.ts +++ b/erdblick_app/app/features.model.ts @@ -14,6 +14,7 @@ export class FeatureTile { mapName: string; layerName: string; tileId: bigint; + legalInfo: string; numFeatures: number; private parser: TileLayerParser; preventCulling: boolean; @@ -39,6 +40,7 @@ export class FeatureTile { this.mapName = mapTileMetadata.mapName; this.layerName = mapTileMetadata.layerName; this.tileId = mapTileMetadata.tileId; + this.legalInfo = mapTileMetadata.legalInfo; this.numFeatures = mapTileMetadata.numFeatures; this.stats.set(FeatureTile.statTileSize, [tileFeatureLayerBlob.length/1024]); for (let [k, v] of Object.entries(mapTileMetadata.scalarFields)) { diff --git a/erdblick_app/app/legalinfo.component.ts b/erdblick_app/app/legalinfo.component.ts new file mode 100644 index 00000000..ed5501b1 --- /dev/null +++ b/erdblick_app/app/legalinfo.component.ts @@ -0,0 +1,74 @@ +import { Component } from "@angular/core"; +import { MapService } from "./map.service"; +import { ParametersService } from "./parameters.service"; + +@Component({ + selector: 'legal-dialog', + template: ` + +
+ + + + + + + + + + + + + +
Map NameLegal Information
{{ info.mapName }}{{ info.entry }}
+ +
+
+ `, + styles: [ + ` + .dialog-content { + display: flex; + flex-direction: column; + gap: 1em; + } + .stats-table { + width: 100%; + border-collapse: collapse; + } + .stats-table th, .stats-table td { + border: 1px solid #ccc; + padding: 0.5em; + text-align: left; + } + .stats-table th { + background-color: #f9f9f9; + font-weight: bold; + } + ` + ] +}) +export class LegalInfoDialogComponent { + public aggregatedLegalInfo: { mapName: string, entry: string }[] = []; + + constructor(private mapService: MapService, + public parametersService: ParametersService) { + this.mapService.legalInformationUpdated.subscribe(_ => { + this.aggregatedLegalInfo = []; + this.mapService.legalInformationPerMap.forEach((entries, mapName) => { + if (entries.size) { + this.aggregatedLegalInfo.push({ + mapName: mapName, + entry: Array.from(entries).join('\n\n') + }) + } + }); + }); + } + + close() { + this.parametersService.legalInfoDialogVisible = false; + } +} + diff --git a/erdblick_app/app/map.service.ts b/erdblick_app/app/map.service.ts index 255327f5..2801f2c5 100644 --- a/erdblick_app/app/map.service.ts +++ b/erdblick_app/app/map.service.ts @@ -86,6 +86,8 @@ export class MapService { public maps: BehaviorSubject> = new BehaviorSubject>(new Map()); public loadedTileLayers: Map; + public legalInformationPerMap = new Map>(); + public legalInformationUpdated = new Subject(); private visualizedTileLayers: Map; private currentFetch: Fetch|null = null; private currentFetchAbort: Fetch|null = null; @@ -602,6 +604,12 @@ export class MapService { this.loadedTileLayers.set(tileLayer.mapTileKey, tileLayer); this.statsDialogNeedsUpdate.next(); + // Update legal information if any. + if (tileLayer.legalInfo) { + console.log("Legal info", tileLayer.legalInfo); + this.setLegalInfo(tileLayer.mapName, tileLayer.legalInfo); + } + // Schedule the visualization of the newly added tile layer, // but don't do it synchronously to avoid stalling the main thread. setTimeout(() => { @@ -860,4 +868,25 @@ export class MapService { } } } + + private setLegalInfo(mapName: string, legalInfo: string): void { + if (this.legalInformationPerMap.has(mapName)) { + this.legalInformationPerMap.get(mapName)!.add(legalInfo); + } else { + this.legalInformationPerMap.set(mapName, new Set().add(legalInfo)); + } + this.legalInformationUpdated.next(true); + } + + private removeLegalInfo(mapName: string): void { + if (this.legalInformationPerMap.has(mapName)) { + this.legalInformationPerMap.delete(mapName); + this.legalInformationUpdated.next(true); + } + } + + private clearAllLegalInfo(): void { + this.legalInformationPerMap.clear(); + this.legalInformationUpdated.next(true); + } } diff --git a/erdblick_app/app/parameters.service.ts b/erdblick_app/app/parameters.service.ts index 0e09841e..92fd6b32 100644 --- a/erdblick_app/app/parameters.service.ts +++ b/erdblick_app/app/parameters.service.ts @@ -218,6 +218,8 @@ export class ParametersService { private baseCameraZoomM = 100.0; private scalingFactor = 1; + legalInfoDialogVisible: boolean = false; + constructor(public router: Router) { this.baseFontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize); diff --git a/erdblick_app/styles.scss b/erdblick_app/styles.scss index 7c57110f..2cbc2d33 100644 --- a/erdblick_app/styles.scss +++ b/erdblick_app/styles.scss @@ -79,6 +79,29 @@ body { line-height: 1.5em; padding-bottom: 0.25em; border-radius: 5px 0 0 0; + height: 1.75em; + width: 10em; +} + +#copyright-info { + position: absolute; + right: 0; + bottom: 1.75em; + justify-content: center; + display: flex; + font-size: 0.8em; + background-color: rgba(#fff, 0.25); + color: black; + padding-right: 0.5em; + padding-left: 0.5em; + line-height: 1.5em; + padding-bottom: 0.25em; + border-radius: 5px 0 0 5px; + overflow: hidden; + height: 1.75em; + width: 10em; + z-index: 100; + cursor: pointer; } .help-button { diff --git a/libs/core/include/erdblick/layer.h b/libs/core/include/erdblick/layer.h index 7e1c0a5d..a5634661 100644 --- a/libs/core/include/erdblick/layer.h +++ b/libs/core/include/erdblick/layer.h @@ -41,6 +41,12 @@ struct TileFeatureLayer */ mapget::Point center() const; + /** + * Retrieves the legal information / copyright of the tile feature layer as a string. + * @return The legal information string. + */ + std::string legalInfo() const; + /** * Finds a feature within the tile by its ID. * @param id The ID of the feature to find. diff --git a/libs/core/include/erdblick/parser.h b/libs/core/include/erdblick/parser.h index be7339c2..9445aadb 100644 --- a/libs/core/include/erdblick/parser.h +++ b/libs/core/include/erdblick/parser.h @@ -49,6 +49,7 @@ class TileLayerParser std::string mapName; std::string layerName; uint64_t tileId; + std::string legalInfo; int32_t numFeatures; NativeJsValue scalarFields; }; diff --git a/libs/core/src/bindings.cpp b/libs/core/src/bindings.cpp index 51efc67b..4ec05563 100644 --- a/libs/core/src/bindings.cpp +++ b/libs/core/src/bindings.cpp @@ -449,6 +449,7 @@ EMSCRIPTEN_BINDINGS(erdblick) .field("mapName", &TileLayerParser::TileLayerMetadata::mapName) .field("layerName", &TileLayerParser::TileLayerMetadata::layerName) .field("tileId", &TileLayerParser::TileLayerMetadata::tileId) + .field("legalInfo", &TileLayerParser::TileLayerMetadata::legalInfo) .field("numFeatures", &TileLayerParser::TileLayerMetadata::numFeatures) .field("scalarFields", &TileLayerParser::TileLayerMetadata::scalarFields); diff --git a/libs/core/src/layer.cpp b/libs/core/src/layer.cpp index 5ce931e3..13c82a69 100644 --- a/libs/core/src/layer.cpp +++ b/libs/core/src/layer.cpp @@ -51,6 +51,15 @@ mapget::Point TileFeatureLayer::center() const return result; } +/** + * Retrieves the legal information / copyright of the tile feature layer as a string. + * @return The legal information string. + */ +std::string TileFeatureLayer::legalInfo() const +{ + return model_->legalInfo() ? *model_->legalInfo() : ""; +} + /** * Finds a feature within the tile by its ID. * @param id The ID of the feature to find. diff --git a/libs/core/src/parser.cpp b/libs/core/src/parser.cpp index 0bf080c3..fb0bd317 100644 --- a/libs/core/src/parser.cpp +++ b/libs/core/src/parser.cpp @@ -151,6 +151,7 @@ TileLayerParser::TileLayerMetadata TileLayerParser::readTileLayerMetadata(const tileLayer.id().mapId_, tileLayer.id().layerId_, tileLayer.tileId().value_, + tileLayer.legalInfo() ? *tileLayer.legalInfo() : "", numFeatures, *allScalarFields };