1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-10 04:07:35 +02:00

Add dark theme to map and make it switch when theme switches #587 #68 #140

This commit is contained in:
Patrik J. Braun 2023-03-14 22:31:58 +01:00
parent 70119241e1
commit 44978f8b53
9 changed files with 144 additions and 69 deletions

View File

@ -181,6 +181,8 @@ export class UserConfig {
role: UserRoles = UserRoles.User;
@ConfigProperty<string, ServerConfig, TAGS>({
type: 'string',
constraint: {assert: (val: string, config) => !!val, assertReason: 'Password cant be empty'},
tags:
{
name: $localize`Password`,

View File

@ -237,6 +237,14 @@ export class MapLayers {
description: $localize`Url of a map layer.`,
})
url: string = '';
@ConfigProperty({
tags:
{
priority: ConfigPriority.advanced,
},
description: $localize`Sets if the layer is dark (used as default in the dark mode).`,
})
darkLayer: boolean = false;
}
@SubConfigClass({tags: {client: true}, softReadonly: true})

View File

@ -91,15 +91,15 @@
<div class="dropdown-item bg-transparent">
<button class="btn w-100 btn-secondary" (click)="themeService.toggleMode()">
<ng-container [ngSwitch]="themeService.mode">
<ng-container *ngSwitchCase="ThemeMode.light">
<ng-container *ngSwitchCase="ThemeModes.light">
<span class="oi oi-sun"></span>
<ng-container i18n>Light</ng-container>
</ng-container>
<ng-container *ngSwitchCase="ThemeMode.dark">
<ng-container *ngSwitchCase="ThemeModes.dark">
<span class="oi oi-moon"></span>
<ng-container i18n>Dark</ng-container>
</ng-container>
<ng-container *ngSwitchCase="ThemeMode.auto">
<ng-container *ngSwitchCase="ThemeModes.auto">
<span class="oi oi-moon"></span>
<span class="oi oi-sun" style="margin-left: -0.8rem"></span>
<ng-container i18n>Auto</ng-container>

View File

@ -6,13 +6,13 @@ import {Config} from '../../../../common/config/public/Config';
import {BehaviorSubject} from 'rxjs';
import {NotificationService} from '../../model/notification.service';
import {QueryService} from '../../model/query.service';
import {NavigationLinkTypes} from '../../../../common/config/public/ClientConfig';
import {NavigationLinkTypes, ThemeModes} from '../../../../common/config/public/ClientConfig';
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
import {Utils} from '../../../../common/Utils';
import {PageHelper} from '../../model/page.helper';
import {BsDropdownDirective} from 'ngx-bootstrap/dropdown';
import {LanguageComponent} from '../language/language.component';
import {ThemeMode, ThemeService} from '../../model/theme.service';
import {ThemeService} from '../../model/theme.service';
@Component({
selector: 'app-frame',
@ -46,7 +46,7 @@ export class FrameComponent {
@ViewChild('dropdown', {static: true}) dropdown: BsDropdownDirective;
@ViewChild('languageSelector', {static: true}) languageSelector: LanguageComponent;
ThemeMode = ThemeMode;
ThemeModes = ThemeModes;
constructor(
private authService: AuthenticationService,

View File

@ -10,6 +10,7 @@ import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes,} from '../../..
import {AuthenticationService} from '../../../../model/network/authentication.service';
import {LatLngLiteral, marker, Marker, TileLayer, tileLayer} from 'leaflet';
import {ContentService} from '../../content.service';
import {ThemeService} from '../../../../model/theme.service';
@Component({
selector: 'app-info-panel',
@ -32,13 +33,20 @@ export class InfoPanelLightboxComponent implements OnInit, OnChanges {
public queryService: QueryService,
public galleryService: ContentService,
public mapService: MapService,
private authService: AuthenticationService
private authService: AuthenticationService,
private themeService: ThemeService
) {
this.mapEnabled = Config.Map.enabled;
this.searchEnabled = this.authService.canSearch();
this.baseLayer = tileLayer(mapService.MapLayer, {
attribution: mapService.ShortAttributions,
});
if (this.themeService.darkMode.value) {
this.baseLayer = tileLayer(mapService.DarkMapLayer.url, {
attribution: mapService.ShortAttributions,
});
} else {
this.baseLayer = tileLayer(mapService.MapLayer.url, {
attribution: mapService.ShortAttributions,
});
}
}
get FullPath(): string {

View File

@ -1,20 +1,8 @@
import {
Component,
ElementRef,
HostListener,
Input,
OnChanges,
ViewChild,
} from '@angular/core';
import {Component, ElementRef, HostListener, Input, OnChanges, ViewChild,} from '@angular/core';
import {PhotoDTO} from '../../../../../../common/entities/PhotoDTO';
import {Dimension} from '../../../../model/IRenderable';
import {FullScreenService} from '../../fullscreen.service';
import {
IconThumbnail,
Thumbnail,
ThumbnailBase,
ThumbnailManagerService,
} from '../../thumbnailManager.service';
import {IconThumbnail, Thumbnail, ThumbnailBase, ThumbnailManagerService,} from '../../thumbnailManager.service';
import {MediaIcon} from '../../MediaIcon';
import {Media} from '../../Media';
import {PageHelper} from '../../../../model/page.helper';
@ -40,8 +28,10 @@ import {
Point,
polyline,
tileLayer,
TileLayer
} from 'leaflet';
import {LeafletControlLayersConfig} from '@asymmetrik/ngx-leaflet';
import {ThemeService} from '../../../../model/theme.service';
@Component({
selector: 'app-gallery-map-lightbox',
@ -73,6 +63,8 @@ export class GalleryMapLightboxComponent implements OnChanges {
maxZoom: 2,
center: latLng(0, 0),
};
defLayer: TileLayer;
darkLayer: TileLayer;
private smallIconSize = new Point(
Config.Media.Thumbnail.iconSize * 0.75,
Config.Media.Thumbnail.iconSize * 0.75
@ -122,7 +114,8 @@ export class GalleryMapLightboxComponent implements OnChanges {
constructor(
public fullScreenService: FullScreenService,
private thumbnailService: ThumbnailManagerService,
public mapService: MapService
public mapService: MapService,
private themeService: ThemeService
) {
this.mapOptions.layers = [
this.mapLayersControlOption.overlays.Photos,
@ -131,19 +124,43 @@ export class GalleryMapLightboxComponent implements OnChanges {
for (let i = 0; i < mapService.Layers.length; ++i) {
const l = mapService.Layers[i];
const tl = tileLayer(l.url, {attribution: mapService.Attributions});
if (i === 0) {
this.mapOptions.layers.push(tl);
if (l.url === mapService.MapLayer.url) {
this.defLayer = tl;
}
if (l.url === mapService.DarkMapLayer.url) {
this.darkLayer = tl;
}
this.mapLayersControlOption.baseLayers[l.name] = tl;
}
if (!this.defLayer || !this.darkLayer) {
throw new Error('Cant find default or dark layer');
}
this.mapOptions.layers.push(this.themeService.darkMode.value ? this.darkLayer : this.defLayer);
this.mapLayerControl = control.layers(
this.mapLayersControlOption.baseLayers,
this.mapLayersControlOption.overlays,
{position: 'bottomright'}
);
// update map theme on dark theme
this.themeService.darkMode.subscribe(this.selectBaseLayer);
}
private selectBaseLayer = () => {
if (!this.leafletMap) {
return;
}
if (this.leafletMap.hasLayer(this.defLayer) && this.themeService.darkMode.value) {
this.leafletMap.removeLayer(this.defLayer);
this.leafletMap.addLayer(this.darkLayer);
}
if (this.leafletMap.hasLayer(this.darkLayer) && !this.themeService.darkMode.value) {
this.leafletMap.removeLayer(this.darkLayer);
this.leafletMap.addLayer(this.defLayer);
}
};
private static getScreenWidth(): number {
return window.innerWidth;
}

View File

@ -1,24 +1,12 @@
import {
Component,
ElementRef,
Input,
OnChanges,
ViewChild,
} from '@angular/core';
import { PhotoDTO } from '../../../../../common/entities/PhotoDTO';
import { Dimension, IRenderable } from '../../../model/IRenderable';
import { GalleryMapLightboxComponent } from './lightbox/lightbox.map.gallery.component';
import { FileDTO } from '../../../../../common/entities/FileDTO';
import { MapService } from './map.service';
import { Config } from '../../../../../common/config/public/Config';
import {
LatLngLiteral,
Map,
MapOptions,
Marker,
marker,
tileLayer,
} from 'leaflet';
import {Component, ElementRef, Input, OnChanges, ViewChild,} from '@angular/core';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {Dimension, IRenderable} from '../../../model/IRenderable';
import {GalleryMapLightboxComponent} from './lightbox/lightbox.map.gallery.component';
import {FileDTO} from '../../../../../common/entities/FileDTO';
import {MapService} from './map.service';
import {Config} from '../../../../../common/config/public/Config';
import {LatLngLiteral, Map, MapOptions, Marker, marker, tileLayer, TileLayer} from 'leaflet';
import {ThemeService} from '../../../model/theme.service';
@Component({
selector: 'app-gallery-map',
@ -28,11 +16,12 @@ import {
export class GalleryMapComponent implements OnChanges, IRenderable {
@Input() photos: PhotoDTO[];
@Input() gpxFiles: FileDTO[];
@ViewChild(GalleryMapLightboxComponent, { static: false })
@ViewChild(GalleryMapLightboxComponent, {static: false})
mapLightbox: GalleryMapLightboxComponent;
@ViewChild('map', { static: false }) mapElement: ElementRef;
@ViewChild('map', {static: false}) mapElement: ElementRef;
leafletMap: Map;
layers: { light: TileLayer, dark: TileLayer };
options: MapOptions = {
zoomControl: false,
@ -46,12 +35,46 @@ export class GalleryMapComponent implements OnChanges, IRenderable {
};
markerLayer: Marker[] = [];
constructor(public mapService: MapService) {
this.options.layers = [
tileLayer(mapService.MapLayer, {
attribution: mapService.ShortAttributions,
constructor(public mapService: MapService,
private themeService: ThemeService) {
this.initThemeModes();
}
initThemeModes() {
this.layers = {
'light': tileLayer(this.mapService.MapLayer.url, {
attribution: this.mapService.ShortAttributions,
}),
];
'dark':
tileLayer(this.mapService.DarkMapLayer.url, {
attribution: this.mapService.ShortAttributions,
})
};
if (this.themeService.darkMode.value) {
this.options.layers = [this.layers.dark];
} else {
this.options.layers = [this.layers.light];
}
// update map theme on dark theme
this.themeService.darkMode.subscribe((isDark) => {
if (!this.leafletMap) {
return;
}
if (isDark) {
if (this.leafletMap.hasLayer(this.layers.dark)) {
return;
}
this.leafletMap.removeLayer(this.layers.light);
this.leafletMap.addLayer(this.layers.dark);
} else {
if (this.leafletMap.hasLayer(this.layers.light)) {
return;
}
this.leafletMap.removeLayer(this.layers.dark);
this.leafletMap.addLayer(this.layers.light);
}
});
}
onMapReady(map: Map): void {

View File

@ -3,18 +3,16 @@ import {NetworkService} from '../../../model/network/network.service';
import {FileDTO} from '../../../../../common/entities/FileDTO';
import {Utils} from '../../../../../common/Utils';
import {Config} from '../../../../../common/config/public/Config';
import {
MapLayers,
MapProviders,
} from '../../../../../common/config/public/ClientConfig';
import {MapLayers, MapProviders,} from '../../../../../common/config/public/ClientConfig';
import {LatLngLiteral} from 'leaflet';
@Injectable()
export class MapService {
private static readonly OSMLAYERS = [
private static readonly OSMLAYERS: MapLayers[] = [
{
name: 'street',
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
darkLayer: false
},
];
private static MAPBOXLAYERS: MapLayers[] = [];
@ -22,22 +20,32 @@ export class MapService {
constructor(private networkService: NetworkService) {
MapService.MAPBOXLAYERS = [
{
name: 'street',
name: $localize`street`,
url:
'https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}?access_token=' +
'https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/256/{z}/{x}/{y}?access_token=' +
Config.Map.mapboxAccessToken,
darkLayer: false
},
{
name: 'satellite',
name: $localize`satellite`,
url:
'https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/256/{z}/{x}/{y}?access_token=' +
Config.Map.mapboxAccessToken,
darkLayer: false
},
{
name: 'hybrid',
name: $localize`hybrid`,
url:
'https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/256/{z}/{x}/{y}?access_token=' +
'https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/256/{z}/{x}/{y}?access_token=' +
Config.Map.mapboxAccessToken,
darkLayer: false
},
{
name: $localize`dark`,
url:
'https://api.mapbox.com/styles/v1/mapbox/navigation-night-v1/tiles/256/{z}/{x}/{y}?access_token=' +
Config.Map.mapboxAccessToken,
darkLayer: true
},
];
}
@ -71,11 +79,15 @@ export class MapService {
return '';
}
public get MapLayer(): string {
return this.Layers[0].url;
public get MapLayer(): MapLayers {
return (this.Layers.find(ml => !ml.darkLayer) || this.Layers[0]);
}
public get Layers(): { name: string; url: string }[] {
public get DarkMapLayer(): MapLayers {
return (this.Layers.find(ml => ml.darkLayer) || this.MapLayer);
}
public get Layers(): MapLayers[] {
switch (Config.Map.mapProvider) {
case MapProviders.Custom:
return Config.Map.customLayers;

View File

@ -203,7 +203,7 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting
}
return ( !forcedVisibility &&
return (!forcedVisibility &&
Utils.equalsFilter(state.value, state.default,
['__propPath', '__created', '__prototype', '__rootConfig']) &&
Utils.equalsFilter(state.original, state.default,
@ -316,6 +316,11 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting
return s[a].tags?.priority - s[b].tags?.priority;
}
if (a === 'enabled' && b !== 'enabled') {
return -1;
} else if (b === 'enabled') {
return 1;
}
return (s[a].tags?.name as string || a).localeCompare(s[b].tags?.name || b);
});