mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
implementing Information panel for showing Exif info at lightbox
This commit is contained in:
parent
2c315d7bd5
commit
3f9c8a383e
@ -65,7 +65,7 @@ To configure it. Run `PiGallery2` first to create `config.json` file, then edit
|
|||||||
* Custom lightbox for full screen photo viewing
|
* Custom lightbox for full screen photo viewing
|
||||||
* keyboard support for navigation
|
* keyboard support for navigation
|
||||||
* showing low-res thumbnail while full image loads
|
* showing low-res thumbnail while full image loads
|
||||||
* Information panel for showing **Exif info** - `In progress`
|
* Information panel for showing **Exif info**
|
||||||
* Client side caching (directories and search results)
|
* Client side caching (directories and search results)
|
||||||
* Rendering **photos** with GPS coordinates **on google map**
|
* Rendering **photos** with GPS coordinates **on google map**
|
||||||
* .gpx file support - `future plan`
|
* .gpx file support - `future plan`
|
||||||
|
@ -34,7 +34,7 @@ export class AuthenticationMWs {
|
|||||||
public static async authenticate(req: Request, res: Response, next: NextFunction) {
|
public static async authenticate(req: Request, res: Response, next: NextFunction) {
|
||||||
|
|
||||||
if (Config.Client.authenticationRequired === false) {
|
if (Config.Client.authenticationRequired === false) {
|
||||||
req.session.user = <UserDTO>{name: "", role: UserRoles.Admin};
|
req.session.user = <UserDTO>{name: "Admin", role: UserRoles.Admin};
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import {Entity, EmbeddableEntity, Column, Embedded, PrimaryGeneratedColumn, ManyToOne} from "typeorm";
|
import {Column, EmbeddableEntity, Embedded, Entity, ManyToOne, PrimaryGeneratedColumn} from "typeorm";
|
||||||
import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
|
import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
|
||||||
import {
|
import {
|
||||||
PhotoDTO,
|
CameraMetadata,
|
||||||
PhotoMetadata,
|
ImageSize,
|
||||||
CameraMetadata,
|
PhotoDTO,
|
||||||
ImageSize,
|
PhotoMetadata,
|
||||||
PositionMetaData
|
PositionMetaData
|
||||||
} from "../../../../common/entities/PhotoDTO";
|
} from "../../../../common/entities/PhotoDTO";
|
||||||
import {DirectoryEntity} from "./DirectoryEntity";
|
import {DirectoryEntity} from "./DirectoryEntity";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class PhotoEntity implements PhotoDTO {
|
export class PhotoEntity implements PhotoDTO {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Column("string")
|
@Column("string")
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ManyToOne(type => DirectoryEntity, directory => directory.photos)
|
@ManyToOne(type => DirectoryEntity, directory => directory.photos)
|
||||||
directory: DirectoryDTO;
|
directory: DirectoryDTO;
|
||||||
|
|
||||||
@Embedded(type => PhotoMetadataEntity)
|
@Embedded(type => PhotoMetadataEntity)
|
||||||
metadata: PhotoMetadataEntity;
|
metadata: PhotoMetadataEntity;
|
||||||
|
|
||||||
readyThumbnails: Array<number> = [];
|
readyThumbnails: Array<number> = [];
|
||||||
|
|
||||||
readyIcon: boolean = false;
|
readyIcon: boolean = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,20 +34,23 @@ export class PhotoEntity implements PhotoDTO {
|
|||||||
@EmbeddableEntity()
|
@EmbeddableEntity()
|
||||||
export class PhotoMetadataEntity implements PhotoMetadata {
|
export class PhotoMetadataEntity implements PhotoMetadata {
|
||||||
|
|
||||||
@Column("string")
|
@Column("string")
|
||||||
keywords: Array<string>;
|
keywords: Array<string>;
|
||||||
|
|
||||||
@Column("string")
|
@Column("string")
|
||||||
cameraData: CameraMetadata;
|
cameraData: CameraMetadata;
|
||||||
|
|
||||||
@Column("string")
|
@Column("string")
|
||||||
positionData: PositionMetaData;
|
positionData: PositionMetaData;
|
||||||
|
|
||||||
@Column("string")
|
@Column("string")
|
||||||
size: ImageSize;
|
size: ImageSize;
|
||||||
|
|
||||||
@Column("number")
|
@Column("number")
|
||||||
creationDate: number;
|
creationDate: number;
|
||||||
|
|
||||||
|
@Column("number")
|
||||||
|
fileSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -113,4 +116,4 @@ export class PhotoMetadataEntity implements PhotoMetadata {
|
|||||||
|
|
||||||
@Column("int")
|
@Column("int")
|
||||||
height: number;
|
height: number;
|
||||||
}*/
|
}*/
|
||||||
|
@ -32,7 +32,7 @@ export class DiskMangerWorker {
|
|||||||
|
|
||||||
private static loadPhotoMetadata(fullPath: string): Promise<PhotoMetadata> {
|
private static loadPhotoMetadata(fullPath: string): Promise<PhotoMetadata> {
|
||||||
return new Promise<PhotoMetadata>((resolve, reject) => {
|
return new Promise<PhotoMetadata>((resolve, reject) => {
|
||||||
fs.readFile(fullPath, function (err, data) {
|
fs.readFile(fullPath, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject({file: fullPath, error: err});
|
return reject({file: fullPath, error: err});
|
||||||
}
|
}
|
||||||
@ -41,11 +41,15 @@ export class DiskMangerWorker {
|
|||||||
cameraData: {},
|
cameraData: {},
|
||||||
positionData: null,
|
positionData: null,
|
||||||
size: {},
|
size: {},
|
||||||
creationDate: 0
|
creationDate: 0,
|
||||||
|
fileSize: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
fs.stat(fullPath, (err, data) => {
|
||||||
|
metadata.fileSize = data.size;
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const exif = exif_parser.create(data).parse();
|
const exif = exif_parser.create(data).parse();
|
||||||
metadata.cameraData = <CameraMetadata> {
|
metadata.cameraData = <CameraMetadata> {
|
||||||
|
@ -15,6 +15,7 @@ export interface PhotoMetadata {
|
|||||||
positionData: PositionMetaData;
|
positionData: PositionMetaData;
|
||||||
size: ImageSize;
|
size: ImageSize;
|
||||||
creationDate: number;
|
creationDate: number;
|
||||||
|
fileSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImageSize {
|
export interface ImageSize {
|
||||||
|
@ -44,6 +44,7 @@ import {NotificationService} from "./model/notification.service";
|
|||||||
|
|
||||||
import {ClipboardModule} from "ngx-clipboard";
|
import {ClipboardModule} from "ngx-clipboard";
|
||||||
import {NavigationService} from "./model/navigation.service";
|
import {NavigationService} from "./model/navigation.service";
|
||||||
|
import {InfoPanelLightboxComponent} from "./gallery/lightbox/infopanel/info-panel.lightbox.gallery.component";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GoogleMapsConfig {
|
export class GoogleMapsConfig {
|
||||||
@ -86,6 +87,7 @@ export class GoogleMapsConfig {
|
|||||||
GalleryNavigatorComponent,
|
GalleryNavigatorComponent,
|
||||||
GalleryPhotoComponent,
|
GalleryPhotoComponent,
|
||||||
AdminComponent,
|
AdminComponent,
|
||||||
|
InfoPanelLightboxComponent,
|
||||||
//Settings
|
//Settings
|
||||||
UserMangerSettingsComponent,
|
UserMangerSettingsComponent,
|
||||||
DatabaseSettingsComponent,
|
DatabaseSettingsComponent,
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
.content {
|
||||||
|
background-color: #F7F7F7;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-icon {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-main {
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-sub {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-sub div {
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sebm-google-map-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
<div class="content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1>Info</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<span class="details-icon glyphicon glyphicon-picture"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="details-main">
|
||||||
|
{{photo.name}}
|
||||||
|
</div>
|
||||||
|
<div class="details-sub">
|
||||||
|
<div class="col-sm-4">{{photo.metadata.size.width}} x {{photo.metadata.size.height}}</div>
|
||||||
|
<div class="col-sm-4">{{calcMpx()}}MP</div>
|
||||||
|
<div class="col-sm-4" *ngIf="photo.metadata.fileSize">{{calcFileSize()}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<span class="details-icon glyphicon glyphicon-calendar"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="details-main">
|
||||||
|
<ng-container *ngIf="getYear() !== getCurrentYear()">
|
||||||
|
{{getYear()}}
|
||||||
|
</ng-container>
|
||||||
|
{{getDate()}}
|
||||||
|
</div>
|
||||||
|
<div class="details-sub">
|
||||||
|
<div class="col-sm-12">{{getDay()}}, {{getTime()}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<span class="details-icon glyphicon glyphicon-camera"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="details-main">
|
||||||
|
{{photo.metadata.cameraData.model || "Camera"}}
|
||||||
|
</div>
|
||||||
|
<div class="details-sub">
|
||||||
|
<div class="col-sm-3" *ngIf="photo.metadata.cameraData.ISO">ISO{{photo.metadata.cameraData.ISO}}</div>
|
||||||
|
<div class="col-sm-3" *ngIf="photo.metadata.cameraData.fStop">f/{{photo.metadata.cameraData.fStop}}</div>
|
||||||
|
<div class="col-sm-3" *ngIf="photo.metadata.cameraData.exposure">
|
||||||
|
{{toFraction(photo.metadata.cameraData.exposure)}}s
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3" *ngIf="photo.metadata.cameraData.focalLength">
|
||||||
|
{{photo.metadata.cameraData.focalLength}}mm
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12" *ngIf="photo.metadata.cameraData.lens">{{photo.metadata.cameraData.lens}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<span class="details-icon glyphicon glyphicon-map-marker"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="details-main">
|
||||||
|
{{getPositionText() || "Position"}}
|
||||||
|
</div>
|
||||||
|
<div class="details-sub">
|
||||||
|
<div class="col-sm-12"
|
||||||
|
*ngIf="hasGPS()">
|
||||||
|
{{photo.metadata.positionData.GPSData.latitude.toFixed(3)}},
|
||||||
|
{{photo.metadata.positionData.GPSData.longitude.toFixed(3)}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="map" *ngIf="hasGPS()">
|
||||||
|
<agm-map
|
||||||
|
[disableDefaultUI]="true"
|
||||||
|
[zoomControl]="false"
|
||||||
|
[streetViewControl]="false"
|
||||||
|
[zoom]="5"
|
||||||
|
[latitude]="photo.metadata.positionData.GPSData.latitude"
|
||||||
|
[longitude]="photo.metadata.positionData.GPSData.longitude">
|
||||||
|
<agm-marker
|
||||||
|
[latitude]="photo.metadata.positionData.GPSData.latitude"
|
||||||
|
[longitude]="photo.metadata.positionData.GPSData.longitude">
|
||||||
|
</agm-marker>
|
||||||
|
</agm-map>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,84 @@
|
|||||||
|
import {Component, Input} from "@angular/core";
|
||||||
|
import {PhotoDTO} from "../../../../../common/entities/PhotoDTO";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'info-panel',
|
||||||
|
styleUrls: ['./info-panel.lightbox.gallery.component.css'],
|
||||||
|
templateUrl: './info-panel.lightbox.gallery.component.html',
|
||||||
|
})
|
||||||
|
export class InfoPanelLightboxComponent {
|
||||||
|
@Input() photo: PhotoDTO;
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
calcMpx() {
|
||||||
|
return (this.photo.metadata.size.width * this.photo.metadata.size.height / 1000000).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
calcFileSize() {
|
||||||
|
let postFixes = ["B", "KB", "MB", "GB", "TB"];
|
||||||
|
let index = 0;
|
||||||
|
let size = this.photo.metadata.fileSize;
|
||||||
|
while (size > 1000 && index < postFixes.length - 1) {
|
||||||
|
size /= 1000;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return size.toFixed(2) + postFixes[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentYear() {
|
||||||
|
return (new Date()).getFullYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
getYear() {
|
||||||
|
const date = new Date(this.photo.metadata.creationDate);
|
||||||
|
return date.getFullYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDate() {
|
||||||
|
const date = new Date(this.photo.metadata.creationDate);
|
||||||
|
let locale = "en-us";
|
||||||
|
return date.toLocaleString(locale, {month: "long"}) + " " + date.getDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTime() {
|
||||||
|
const date = new Date(this.photo.metadata.creationDate);
|
||||||
|
return date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDay() {
|
||||||
|
const date = new Date(this.photo.metadata.creationDate);
|
||||||
|
let locale = "en-us";
|
||||||
|
return date.toLocaleString(locale, {weekday: "long"});
|
||||||
|
}
|
||||||
|
|
||||||
|
toFraction(f) {
|
||||||
|
if (f > 1) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
return "1/" + Math.ceil(((f < 1.0) ? f : (f % Math.floor(f))) * 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasGPS() {
|
||||||
|
return this.photo.metadata.positionData && this.photo.metadata.positionData.GPSData &&
|
||||||
|
this.photo.metadata.positionData.GPSData.latitude && this.photo.metadata.positionData.GPSData.longitude
|
||||||
|
}
|
||||||
|
|
||||||
|
getPositionText(): string {
|
||||||
|
if (!this.photo.metadata.positionData) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
let str = this.photo.metadata.positionData.city ||
|
||||||
|
this.photo.metadata.positionData.state;
|
||||||
|
|
||||||
|
if (str.length != 0) {
|
||||||
|
str += ", ";
|
||||||
|
}
|
||||||
|
str += this.photo.metadata.positionData.country;
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,89 +1,96 @@
|
|||||||
.lightbox {
|
.lightbox {
|
||||||
position: fixed; /* Stay in place */
|
position: fixed; /* Stay in place */
|
||||||
z-index: 1100; /* Sit on top */
|
z-index: 1100; /* Sit on top */
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%; /* Full width */
|
width: 100%; /* Full width */
|
||||||
height: 100%; /* Full height */
|
height: 100%; /* Full height */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex; /* add */
|
display: flex; /* add */
|
||||||
justify-content: center; /* add to align horizontal */
|
justify-content: center; /* add to align horizontal */
|
||||||
align-items: center; /* add to align vertical */
|
align-items: center; /* add to align vertical */
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
gallery-lightbox-photo {
|
gallery-lightbox-photo {
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blackCanvas{
|
.blackCanvas {
|
||||||
position: fixed; /* Stay in place */
|
position: fixed; /* Stay in place */
|
||||||
z-index: 1099; /* Sit on top */
|
z-index: 1099; /* Sit on top */
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%; /* Full width */
|
width: 100%; /* Full width */
|
||||||
height: 100%; /* Full height */
|
height: 100%; /* Full height */
|
||||||
background-color: black;
|
background-color: black;
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.navigation-arrow {
|
.navigation-arrow {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: static;
|
position: static;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: x-large;
|
font-size: x-large;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-arrow span {
|
.navigation-arrow span {
|
||||||
top: 43%;
|
top: 43%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controllers-container {
|
#controllers-container {
|
||||||
z-index: 1100;
|
z-index: 1100;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
position: fixed;;
|
position: fixed;
|
||||||
color: white;
|
color: white;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rightArrow {
|
#rightArrow {
|
||||||
float: right;
|
float: right;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controls {
|
#controls {
|
||||||
top: 0;
|
top: 0;
|
||||||
height: initial;
|
height: initial;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controls span {
|
#controls span {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
transition: opacity .2s ease-out;
|
transition: opacity .2s ease-out;
|
||||||
-moz-transition: opacity .2s ease-out;
|
-moz-transition: opacity .2s ease-out;
|
||||||
-webkit-transition: opacity .2s ease-out;
|
-webkit-transition: opacity .2s ease-out;
|
||||||
-o-transition: opacity .2s ease-out;
|
-o-transition: opacity .2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight:hover {
|
.highlight:hover {
|
||||||
opacity: 1.0;
|
opacity: 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info-panel {
|
||||||
|
z-index: 1100; /* Sit on top */
|
||||||
|
position: fixed;
|
||||||
|
height: 100vh;
|
||||||
|
right: 0;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
@ -1,47 +1,54 @@
|
|||||||
<div [hidden]="!visible" #root>
|
<div [hidden]="!visible" #root>
|
||||||
|
|
||||||
<div class="blackCanvas"
|
<div class="blackCanvas"
|
||||||
[style.opacity]="blackCanvasOpacity">
|
[style.opacity]="blackCanvasOpacity">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lightbox"
|
||||||
|
[style.width.px]="lightboxDimension.width"
|
||||||
|
[style.height.px]="lightboxDimension.height"
|
||||||
|
[style.top.px]="lightboxDimension.top"
|
||||||
|
[style.left.px]="lightboxDimension.left">
|
||||||
|
<gallery-lightbox-photo [gridPhoto]="activePhoto ? activePhoto.gridPhoto : null"
|
||||||
|
[style.top.px]="photoDimension.top"
|
||||||
|
[style.left.px]="photoDimension.left"
|
||||||
|
[style.width.px]="photoDimension.width"
|
||||||
|
[style.height.px]="photoDimension.height"
|
||||||
|
[style.transition]="transition">
|
||||||
|
</gallery-lightbox-photo>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="controllers-container" #controls
|
||||||
|
[style.width.px]="contentWidth">
|
||||||
|
<div id="controls">
|
||||||
|
<a *ngIf="activePhoto" [href]="activePhoto.gridPhoto.getPhotoPath()"
|
||||||
|
[download]="activePhoto.gridPhoto.photo.name"><span class="glyphicon glyphicon-download-alt highlight"
|
||||||
|
title="download"></span></a>
|
||||||
|
<span class="glyphicon glyphicon-info-sign highlight" (click)="toggleInfoPanel()" title="info"></span>
|
||||||
|
|
||||||
|
<span class=" glyphicon glyphicon-resize-small highlight"
|
||||||
|
*ngIf="fullScreenService.isFullScreenEnabled()"
|
||||||
|
(click)="fullScreenService.exitFullScreen()" title="toggle fullscreen"></span>
|
||||||
|
|
||||||
|
<span class="glyphicon glyphicon-fullscreen highlight"
|
||||||
|
*ngIf="!fullScreenService.isFullScreenEnabled()"
|
||||||
|
(click)="fullScreenService.showFullScreen(root)" title="toggle fullscreen"></span>
|
||||||
|
|
||||||
|
<span class="glyphicon glyphicon-remove highlight" (click)="hide()" title="close"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="lightbox"
|
<div class="navigation-arrow highlight" *ngIf="navigation.hasPrev" title="key: left arrow" id="leftArrow"
|
||||||
[style.width.px]="lightboxDimension.width"
|
(click)="prevImage()"><span
|
||||||
[style.height.px]="lightboxDimension.height"
|
class="glyphicon glyphicon-chevron-left"></span></div>
|
||||||
[style.top.px]="lightboxDimension.top"
|
<div class="navigation-arrow highlight" *ngIf="navigation.hasNext" title="key: right arrow" id="rightArrow"
|
||||||
[style.left.px]="lightboxDimension.left">
|
(click)="nextImage()"><span
|
||||||
<gallery-lightbox-photo [gridPhoto]="activePhoto ? activePhoto.gridPhoto : null"
|
class="glyphicon glyphicon-chevron-right"></span></div>
|
||||||
[style.top.px]="photoDimension.top"
|
</div>
|
||||||
[style.left.px]="photoDimension.left"
|
<info-panel *ngIf="activePhoto && infoPanelVisible"
|
||||||
[style.width.px]="photoDimension.width"
|
id="info-panel"
|
||||||
[style.height.px]="photoDimension.height"
|
[style.width.px]="infoPanelWidth"
|
||||||
[style.transition]="transition">
|
[photo]="activePhoto.gridPhoto.photo">
|
||||||
</gallery-lightbox-photo>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
</info-panel>
|
||||||
<div id="controllers-container" #controls>
|
|
||||||
<div id="controls">
|
|
||||||
<a *ngIf="activePhoto" [href]="activePhoto.gridPhoto.getPhotoPath()"
|
|
||||||
[download]="activePhoto.gridPhoto.photo.name"><span class="glyphicon glyphicon-download-alt highlight"
|
|
||||||
title="download"></span></a>
|
|
||||||
<span class="glyphicon glyphicon-info-sign highlight" title="info"></span>
|
|
||||||
|
|
||||||
<span class=" glyphicon glyphicon-resize-small highlight"
|
|
||||||
*ngIf="fullScreenService.isFullScreenEnabled()"
|
|
||||||
(click)="fullScreenService.exitFullScreen()" title="toggle fullscreen"></span>
|
|
||||||
|
|
||||||
<span class="glyphicon glyphicon-fullscreen highlight"
|
|
||||||
*ngIf="!fullScreenService.isFullScreenEnabled()"
|
|
||||||
(click)="fullScreenService.showFullScreen(root)" title="toggle fullscreen"></span>
|
|
||||||
|
|
||||||
<span class="glyphicon glyphicon-remove highlight" (click)="hide()" title="close"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="navigation-arrow highlight" *ngIf="navigation.hasPrev" title="key: left arrow" id="leftArrow"
|
|
||||||
(click)="prevImage()"><span
|
|
||||||
class="glyphicon glyphicon-chevron-left"></span></div>
|
|
||||||
<div class="navigation-arrow highlight" *ngIf="navigation.hasNext" title="key: right arrow" id="rightArrow"
|
|
||||||
(click)="nextImage()"><span
|
|
||||||
class="glyphicon glyphicon-chevron-right"></span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
HostListener,
|
HostListener,
|
||||||
|
OnDestroy,
|
||||||
Output,
|
Output,
|
||||||
QueryList,
|
QueryList,
|
||||||
ViewChild
|
ViewChild
|
||||||
@ -20,8 +21,9 @@ import {Subscription} from "rxjs";
|
|||||||
styleUrls: ['./lightbox.gallery.component.css'],
|
styleUrls: ['./lightbox.gallery.component.css'],
|
||||||
templateUrl: './lightbox.gallery.component.html',
|
templateUrl: './lightbox.gallery.component.html',
|
||||||
})
|
})
|
||||||
export class GalleryLightboxComponent {
|
export class GalleryLightboxComponent implements OnDestroy {
|
||||||
@Output('onLastElement') onLastElement = new EventEmitter();
|
@Output('onLastElement') onLastElement = new EventEmitter();
|
||||||
|
@ViewChild("root") elementRef: ElementRef;
|
||||||
|
|
||||||
public navigation = {hasPrev: true, hasNext: true};
|
public navigation = {hasPrev: true, hasNext: true};
|
||||||
public photoDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
|
public photoDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
|
||||||
@ -35,14 +37,23 @@ export class GalleryLightboxComponent {
|
|||||||
public visible = false;
|
public visible = false;
|
||||||
private changeSubscription: Subscription = null;
|
private changeSubscription: Subscription = null;
|
||||||
|
|
||||||
@ViewChild("root") elementRef: ElementRef;
|
|
||||||
|
public infoPanelVisible = false;
|
||||||
|
public infoPanelWidth = 0;
|
||||||
|
public contentWidth = 0;
|
||||||
|
|
||||||
|
|
||||||
constructor(public fullScreenService: FullScreenService, private changeDetector: ChangeDetectorRef, private overlayService: OverlayService) {
|
constructor(public fullScreenService: FullScreenService, private changeDetector: ChangeDetectorRef, private overlayService: OverlayService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//noinspection JSUnusedGlobalSymbols
|
ngOnDestroy(): void {
|
||||||
|
if (this.changeSubscription != null) {
|
||||||
|
this.changeSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection JSUnusedGlobalSymbols
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize() {
|
onResize() {
|
||||||
if (this.activePhoto) {
|
if (this.activePhoto) {
|
||||||
@ -130,11 +141,13 @@ export class GalleryLightboxComponent {
|
|||||||
};
|
};
|
||||||
this.blackCanvasOpacity = 1.0;
|
this.blackCanvasOpacity = 1.0;
|
||||||
this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto));
|
this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto));
|
||||||
|
this.contentWidth = this.getScreenWidth();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public hide() {
|
public hide() {
|
||||||
this.enableAnimation();
|
this.enableAnimation();
|
||||||
|
this.hideInfoPanel();
|
||||||
this.fullScreenService.exitFullScreen();
|
this.fullScreenService.exitFullScreen();
|
||||||
|
|
||||||
this.lightboxDimension = this.activePhoto.getDimension();
|
this.lightboxDimension = this.activePhoto.getDimension();
|
||||||
@ -196,6 +209,52 @@ export class GalleryLightboxComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iPvisibilityTimer = null;
|
||||||
|
|
||||||
|
public toggleInfoPanel() {
|
||||||
|
|
||||||
|
|
||||||
|
if (this.infoPanelWidth != 400) {
|
||||||
|
this.showInfoPanel();
|
||||||
|
} else {
|
||||||
|
this.hideInfoPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
recalcPositions() {
|
||||||
|
|
||||||
|
this.photoDimension = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
|
||||||
|
this.contentWidth = this.getScreenWidth();
|
||||||
|
this.lightboxDimension = <Dimension>{
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: this.getScreenWidth(),
|
||||||
|
height: this.getScreenHeight()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
showInfoPanel() {
|
||||||
|
this.infoPanelVisible = true;
|
||||||
|
this.infoPanelWidth = 0;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.infoPanelWidth = 400;
|
||||||
|
this.recalcPositions();
|
||||||
|
}, 0);
|
||||||
|
if (this.iPvisibilityTimer != null) {
|
||||||
|
clearTimeout(this.iPvisibilityTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideInfoPanel() {
|
||||||
|
this.infoPanelWidth = 0;
|
||||||
|
this.iPvisibilityTimer = setTimeout(() => {
|
||||||
|
this.iPvisibilityTimer = null;
|
||||||
|
this.infoPanelVisible = false;
|
||||||
|
}, 1000);
|
||||||
|
this.recalcPositions();
|
||||||
|
}
|
||||||
|
|
||||||
private enableAnimation() {
|
private enableAnimation() {
|
||||||
this.transition = null;
|
this.transition = null;
|
||||||
}
|
}
|
||||||
@ -214,7 +273,7 @@ export class GalleryLightboxComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getScreenWidth() {
|
private getScreenWidth() {
|
||||||
return window.innerWidth;
|
return Math.max(window.innerWidth - this.infoPanelWidth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getScreenHeight() {
|
private getScreenHeight() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user