diff --git a/angular.json b/angular.json index 46c70b10c..4e41e93e0 100644 --- a/angular.json +++ b/angular.json @@ -92,7 +92,6 @@ "tsConfig": "src/frontend/tsconfig.app.json", "polyfills": "src/frontend/polyfills.ts", "assets": [ - "src/frontend/assets", "src/frontend/robots.txt", { "glob": "**/*", @@ -199,7 +198,6 @@ "src/frontend/styles.css" ], "assets": [ - "src/frontend/assets", { "glob": "**/*", "input": "node_modules/leaflet/dist/images/", diff --git a/src/backend/model/fileprocessing/PhotoProcessing.ts b/src/backend/model/fileprocessing/PhotoProcessing.ts index 5fc84e511..6966edbda 100644 --- a/src/backend/model/fileprocessing/PhotoProcessing.ts +++ b/src/backend/model/fileprocessing/PhotoProcessing.ts @@ -4,15 +4,16 @@ import * as os from 'os'; import * as crypto from 'crypto'; import {ProjectPath} from '../../ProjectPath'; import {Config} from '../../../common/config/private/Config'; -import {PhotoWorker, RendererInput, ThumbnailSourceType,} from '../threading/PhotoWorker'; +import {MediaRendererInput, PhotoWorker, SvgRendererInput, ThumbnailSourceType,} from '../threading/PhotoWorker'; import {ITaskExecuter, TaskExecuter} from '../threading/TaskExecuter'; import {FaceRegion, PhotoDTO} from '../../../common/entities/PhotoDTO'; import {SupportedFormats} from '../../../common/SupportedFormats'; import {PersonEntry} from '../database/enitites/PersonEntry'; +import {SVGIconConfig} from '../../../common/config/public/ClientConfig'; export class PhotoProcessing { private static initDone = false; - private static taskQue: ITaskExecuter = null; + private static taskQue: ITaskExecuter = null; private static readonly CONVERTED_EXTENSION = '.webp'; public static init(): void { @@ -101,7 +102,7 @@ export class PhotoProcessing { useLanczos3: Config.Media.Thumbnail.useLanczos3, quality: Config.Media.Thumbnail.quality, smartSubsample: Config.Media.Thumbnail.smartSubsample, - } as RendererInput; + } as MediaRendererInput; input.cut.width = Math.min( input.cut.width, photo.metadata.size.width - input.cut.left @@ -240,6 +241,7 @@ export class PhotoProcessing { return false; } + public static async generateThumbnail( mediaPath: string, size: number, @@ -267,7 +269,7 @@ export class PhotoProcessing { useLanczos3: Config.Media.Thumbnail.useLanczos3, quality: Config.Media.Thumbnail.quality, smartSubsample: Config.Media.Thumbnail.smartSubsample, - } as RendererInput; + } as MediaRendererInput; const outDir = path.dirname(input.outPath); @@ -280,5 +282,42 @@ export class PhotoProcessing { const extension = path.extname(fullPath).toLowerCase(); return SupportedFormats.WithDots.Photos.indexOf(extension) !== -1; } + + public static async renderSVG( + svgString: SVGIconConfig, + outPath: string, + color = '#000' + ): Promise { + + // check if file already exist + try { + await fsp.access(outPath, fsConstants.R_OK); + return outPath; + } catch (e) { + // ignoring errors + } + + const size = 256; + // run on other thread + const input = { + type: ThumbnailSourceType.Photo, + svgString: ` +`, + size: size, + outPath, + makeSquare: false, + useLanczos3: Config.Media.Thumbnail.useLanczos3, + quality: Config.Media.Thumbnail.quality, + smartSubsample: Config.Media.Thumbnail.smartSubsample, + } as SvgRendererInput; + + const outDir = path.dirname(input.outPath); + + await fsp.mkdir(outDir, {recursive: true}); + await this.taskQue.execute(input); + return outPath; + } + } diff --git a/src/backend/model/threading/PhotoWorker.ts b/src/backend/model/threading/PhotoWorker.ts index 5ff7640c8..b8a8e6765 100644 --- a/src/backend/model/threading/PhotoWorker.ts +++ b/src/backend/model/threading/PhotoWorker.ts @@ -1,32 +1,32 @@ /* eslint-disable @typescript-eslint/no-var-requires */ +import * as sharp from 'sharp'; import {Metadata, Sharp} from 'sharp'; import {Logger} from '../../Logger'; import {FfmpegCommand, FfprobeData} from 'fluent-ffmpeg'; import {FFmpegFactory} from '../FFmpegFactory'; -const path = require('path'); +import * as path from 'path'; -const sharp = require('sharp'); sharp.cache(false); export class PhotoWorker { - private static videoRenderer: (input: RendererInput) => Promise = null; + private static videoRenderer: (input: MediaRendererInput) => Promise = null; - public static render(input: RendererInput): Promise { + public static render(input: SvgRendererInput | MediaRendererInput): Promise { if (input.type === ThumbnailSourceType.Photo) { return this.renderFromImage(input); } if (input.type === ThumbnailSourceType.Video) { - return this.renderFromVideo(input); + return this.renderFromVideo(input as MediaRendererInput); } throw new Error('Unsupported media type to render thumbnail:' + input.type); } - public static renderFromImage(input: RendererInput): Promise { + public static renderFromImage(input: SvgRendererInput | MediaRendererInput): Promise { return ImageRendererFactory.render(input); } - public static renderFromVideo(input: RendererInput): Promise { + public static renderFromVideo(input: MediaRendererInput): Promise { if (PhotoWorker.videoRenderer === null) { PhotoWorker.videoRenderer = VideoRendererFactory.build(); } @@ -39,15 +39,13 @@ export enum ThumbnailSourceType { Video = 2, } -export interface RendererInput { +interface RendererInput { type: ThumbnailSourceType; - mediaPath: string; size: number; makeSquare: boolean; outPath: string; quality: number; useLanczos3: boolean; - smartSubsample: boolean; cut?: { left: number; top: number; @@ -56,10 +54,19 @@ export interface RendererInput { }; } +export interface MediaRendererInput extends RendererInput { + mediaPath: string; + smartSubsample: boolean; +} + +export interface SvgRendererInput extends RendererInput { + svgString: string; +} + export class VideoRendererFactory { - public static build(): (input: RendererInput) => Promise { + public static build(): (input: MediaRendererInput) => Promise { const ffmpeg = FFmpegFactory.get(); - return (input: RendererInput): Promise => { + return (input: MediaRendererInput): Promise => { return new Promise((resolve, reject): void => { Logger.silly('[FFmpeg] rendering thumbnail: ' + input.mediaPath); @@ -122,16 +129,22 @@ export class VideoRendererFactory { export class ImageRendererFactory { - public static async render(input: RendererInput): Promise { - Logger.silly( - '[SharpRenderer] rendering photo:' + - input.mediaPath + - ', size:' + - input.size - ); - const image: Sharp = sharp(input.mediaPath, {failOnError: false}); + public static async render(input: MediaRendererInput | SvgRendererInput): Promise { + + let image: Sharp; + if ((input as MediaRendererInput).mediaPath) { + Logger.silly( + '[SharpRenderer] rendering photo:' + + (input as MediaRendererInput).mediaPath + + ', size:' + + input.size + ); + image = sharp((input as MediaRendererInput).mediaPath, {failOnError: false}); + } else { + const svg_buffer = Buffer.from((input as SvgRendererInput).svgString); + image = sharp(svg_buffer, { density: 450 }); + } const metadata: Metadata = await image.metadata(); - const kernel = input.useLanczos3 === true ? sharp.kernel.lanczos3 @@ -157,7 +170,17 @@ export class ImageRendererFactory { fit: 'cover', }); } - await image.rotate().webp({effort: 6, quality: input.quality, smartSubsample: input.smartSubsample}).toFile(input.outPath); + if ((input as MediaRendererInput).mediaPath) { + await image.rotate().webp({ + effort: 6, + quality: input.quality, + smartSubsample: (input as MediaRendererInput).smartSubsample + }).toFile(input.outPath); + } else { + if ((input as SvgRendererInput).svgString) { + await image.rotate().png({effort: 6, quality: input.quality}).toFile(input.outPath); + } + } } } diff --git a/src/backend/routes/PublicRouter.ts b/src/backend/routes/PublicRouter.ts index 13bb35dd5..39e173818 100644 --- a/src/backend/routes/PublicRouter.ts +++ b/src/backend/routes/PublicRouter.ts @@ -11,6 +11,7 @@ import {UserDTO} from '../../common/entities/UserDTO'; import {ServerTimeEntry} from '../middlewares/ServerTimingMWs'; import {ClientConfig, TAGS} from '../../common/config/public/ClientConfig'; import {QueryParams} from '../../common/QueryParams'; +import {PhotoProcessing} from '../model/fileprocessing/PhotoProcessing'; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -101,7 +102,7 @@ export class PublicRouter { .replace(/'/g, '''); res.tpl.Config = confCopy; res.tpl.customHTMLHead = Config.Server.customHTMLHead; - const selectedTheme = Config.Gallery.Themes.availableThemes.find(th=>th.name === Config.Gallery.Themes.selectedTheme)?.theme || ''; + const selectedTheme = Config.Gallery.Themes.availableThemes.find(th => th.name === Config.Gallery.Themes.selectedTheme)?.theme || ''; res.tpl.usedTheme = selectedTheme; return next(); @@ -118,7 +119,11 @@ export class PublicRouter { name: Config.Server.applicationTitle, icons: [ { - src: 'assets/icon_inv.png', + src: 'icon_inv.svg', + sizes: 'any', + }, + { + src: 'icon_inv.png', sizes: '48x48 72x72 96x96 128x128 256x256', }, ], @@ -133,6 +138,47 @@ export class PublicRouter { }); }); + app.get('/icon.svg', (req: Request, res: Response) => { + res.set('Cache-control', 'public, max-age=31536000'); + res.send('' + + ''); + }); + + app.get('/icon_inv.svg', (req: Request, res: Response) => { + res.set('Cache-control', 'public, max-age=31536000'); + res.send('' + + ''); + }); + + + app.get('/icon.png', async (req: Request, res: Response, next: NextFunction) => { + try { + const p = path.join(ProjectPath.TempFolder, '/icon.png'); + await PhotoProcessing.renderSVG(Config.Server.svgIcon, p); + res.sendFile(p, { + maxAge: 31536000, + dotfiles: 'allow', + }); + } catch (e) { + return next(e); + } + }); + + app.get('/icon_inv.png', async (req: Request, res: Response, next: NextFunction) => { + try { + const p = path.join(ProjectPath.TempFolder, '/icon_inv.png'); + await PhotoProcessing.renderSVG(Config.Server.svgIcon, p, '#FFF'); + res.sendFile(p, { + maxAge: 31536000, + dotfiles: 'allow', + }); + } catch (e) { + return next(e); + } + }); + app.get( [ '/', diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts index d18f773a4..9ce706346 100644 --- a/src/common/config/public/ClientConfig.ts +++ b/src/common/config/public/ClientConfig.ts @@ -1240,6 +1240,19 @@ export class ClientServiceConfig { } }) customHTMLHead: string = ''; + + + @ConfigProperty({ + type: SVGIconConfig, + tags: { + name: $localize`Svg Icon`, + uiType: 'SVGIconConfig', + priority: ConfigPriority.advanced + } as TAGS, + description: $localize`Sets the icon of the app`, + }) + // Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. + svgIcon: SVGIconConfig = new SVGIconConfig(`0 0 407 512`, 'M372 232.5l-3.7-6.5c.1-46.4-21.4-65.3-46.5-79.7 7.6-2 15.4-3.6 17.6-13.2 13.1-3.3 15.8-9.4 17.1-15.8 3.4-2.3 14.8-8.7 13.6-19.7 6.4-4.4 10-10.1 8.1-18.1 6.9-7.5 8.7-13.7 5.8-19.4 8.3-10.3 4.6-15.6 1.1-20.9 6.2-11.2.7-23.2-16.6-21.2-6.9-10.1-21.9-7.8-24.2-7.8-2.6-3.2-6-6-16.5-4.7-6.8-6.1-14.4-5-22.3-2.1-9.3-7.3-15.5-1.4-22.6.8C271.6.6 269 5.5 263.5 7.6c-12.3-2.6-16.1 3-22 8.9l-6.9-.1c-18.6 10.8-27.8 32.8-31.1 44.1-3.3-11.3-12.5-33.3-31.1-44.1l-6.9.1c-5.9-5.9-9.7-11.5-22-8.9-5.6-2-8.1-7-19.4-3.4-4.6-1.4-8.9-4.4-13.9-4.3-2.6.1-5.5 1-8.7 3.5-7.9-3-15.5-4-22.3 2.1-10.5-1.3-14 1.4-16.5 4.7-2.3 0-17.3-2.3-24.2 7.8C21.2 16 15.8 28 22 39.2c-3.5 5.4-7.2 10.7 1.1 20.9-2.9 5.7-1.1 11.9 5.8 19.4-1.8 8 1.8 13.7 8.1 18.1-1.2 11 10.2 17.4 13.6 19.7 1.3 6.4 4 12.4 17.1 15.8 2.2 9.5 10 11.2 17.6 13.2-25.1 14.4-46.6 33.3-46.5 79.7l-3.7 6.5c-28.8 17.2-54.7 72.7-14.2 117.7 2.6 14.1 7.1 24.2 11 35.4 5.9 45.2 44.5 66.3 54.6 68.8 14.9 11.2 30.8 21.8 52.2 29.2C159 504.2 181 512 203 512h1c22.1 0 44-7.8 64.2-28.4 21.5-7.4 37.3-18 52.2-29.2 10.2-2.5 48.7-23.6 54.6-68.8 3.9-11.2 8.4-21.3 11-35.4 40.6-45.1 14.7-100.5-14-117.7zm-22.2-8c-1.5 18.7-98.9-65.1-82.1-67.9 45.7-7.5 83.6 19.2 82.1 67.9zm-43 93.1c-24.5 15.8-59.8 5.6-78.8-22.8s-14.6-64.2 9.9-80c24.5-15.8 59.8-5.6 78.8 22.8s14.6 64.2-9.9 80zM238.9 29.3c.8 4.2 1.8 6.8 2.9 7.6 5.4-5.8 9.8-11.7 16.8-17.3 0 3.3-1.7 6.8 2.5 9.4 3.7-5 8.8-9.5 15.5-13.3-3.2 5.6-.6 7.3 1.2 9.6 5.1-4.4 10-8.8 19.4-12.3-2.6 3.1-6.2 6.2-2.4 9.8 5.3-3.3 10.6-6.6 23.1-8.9-2.8 3.1-8.7 6.3-5.1 9.4 6.6-2.5 14-4.4 22.1-5.4-3.9 3.2-7.1 6.3-3.9 8.8 7.1-2.2 16.9-5.1 26.4-2.6l-6 6.1c-.7.8 14.1.6 23.9.8-3.6 5-7.2 9.7-9.3 18.2 1 1 5.8.4 10.4 0-4.7 9.9-12.8 12.3-14.7 16.6 2.9 2.2 6.8 1.6 11.2.1-3.4 6.9-10.4 11.7-16 17.3 1.4 1 3.9 1.6 9.7.9-5.2 5.5-11.4 10.5-18.8 15 1.3 1.5 5.8 1.5 10 1.6-6.7 6.5-15.3 9.9-23.4 14.2 4 2.7 6.9 2.1 10 2.1-5.7 4.7-15.4 7.1-24.4 10 1.7 2.7 3.4 3.4 7.1 4.1-9.5 5.3-23.2 2.9-27 5.6.9 2.7 3.6 4.4 6.7 5.8-15.4.9-57.3-.6-65.4-32.3 15.7-17.3 44.4-37.5 93.7-62.6-38.4 12.8-73 30-102 53.5-34.3-15.9-10.8-55.9 5.8-71.8zm-34.4 114.6c24.2-.3 54.1 17.8 54 34.7-.1 15-21 27.1-53.8 26.9-32.1-.4-53.7-15.2-53.6-29.8 0-11.9 26.2-32.5 53.4-31.8zm-123-12.8c3.7-.7 5.4-1.5 7.1-4.1-9-2.8-18.7-5.3-24.4-10 3.1 0 6 .7 10-2.1-8.1-4.3-16.7-7.7-23.4-14.2 4.2-.1 8.7 0 10-1.6-7.4-4.5-13.6-9.5-18.8-15 5.8.7 8.3.1 9.7-.9-5.6-5.6-12.7-10.4-16-17.3 4.3 1.5 8.3 2 11.2-.1-1.9-4.2-10-6.7-14.7-16.6 4.6.4 9.4 1 10.4 0-2.1-8.5-5.8-13.3-9.3-18.2 9.8-.1 24.6 0 23.9-.8l-6-6.1c9.5-2.5 19.3.4 26.4 2.6 3.2-2.5-.1-5.6-3.9-8.8 8.1 1.1 15.4 2.9 22.1 5.4 3.5-3.1-2.3-6.3-5.1-9.4 12.5 2.3 17.8 5.6 23.1 8.9 3.8-3.6.2-6.7-2.4-9.8 9.4 3.4 14.3 7.9 19.4 12.3 1.7-2.3 4.4-4 1.2-9.6 6.7 3.8 11.8 8.3 15.5 13.3 4.1-2.6 2.5-6.2 2.5-9.4 7 5.6 11.4 11.5 16.8 17.3 1.1-.8 2-3.4 2.9-7.6 16.6 15.9 40.1 55.9 6 71.8-29-23.5-63.6-40.7-102-53.5 49.3 25 78 45.3 93.7 62.6-8 31.8-50 33.2-65.4 32.3 3.1-1.4 5.8-3.2 6.7-5.8-4-2.8-17.6-.4-27.2-5.6zm60.1 24.1c16.8 2.8-80.6 86.5-82.1 67.9-1.5-48.7 36.5-75.5 82.1-67.9zM38.2 342c-23.7-18.8-31.3-73.7 12.6-98.3 26.5-7 9 107.8-12.6 98.3zm91 98.2c-13.3 7.9-45.8 4.7-68.8-27.9-15.5-27.4-13.5-55.2-2.6-63.4 16.3-9.8 41.5 3.4 60.9 25.6 16.9 20 24.6 55.3 10.5 65.7zm-26.4-119.7c-24.5-15.8-28.9-51.6-9.9-80s54.3-38.6 78.8-22.8 28.9 51.6 9.9 80c-19.1 28.4-54.4 38.6-78.8 22.8zM205 496c-29.4 1.2-58.2-23.7-57.8-32.3-.4-12.7 35.8-22.6 59.3-22 23.7-1 55.6 7.5 55.7 18.9.5 11-28.8 35.9-57.2 35.4zm58.9-124.9c.2 29.7-26.2 53.8-58.8 54-32.6.2-59.2-23.8-59.4-53.4v-.6c-.2-29.7 26.2-53.8 58.8-54 32.6-.2 59.2 23.8 59.4 53.4v.6zm82.2 42.7c-25.3 34.6-59.6 35.9-72.3 26.3-13.3-12.4-3.2-50.9 15.1-72 20.9-23.3 43.3-38.5 58.9-26.6 10.5 10.3 16.7 49.1-1.7 72.3zm22.9-73.2c-21.5 9.4-39-105.3-12.6-98.3 43.9 24.7 36.3 79.6 12.6 98.3z'); } @SubConfigClass({tags: {client: true}, softReadonly: true}) diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index 04effe01f..4b0254d5d 100644 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -80,7 +80,7 @@ import {GallerySearchFieldBaseComponent} from './ui/gallery/search/search-field- import {AppRoutingModule} from './app.routing'; import {CookieService} from 'ngx-cookie-service'; import {LeafletMarkerClusterModule} from '@asymmetrik/ngx-leaflet-markercluster'; -import {icon, Marker} from 'leaflet'; +import {Marker} from 'leaflet'; import {AlbumsComponent} from './ui/albums/albums.component'; import {AlbumComponent} from './ui/albums/album/album.component'; import {AlbumsService} from './ui/albums/albums.service'; @@ -108,6 +108,7 @@ import {ThemeService} from './model/theme.service'; import {StringifyEnum} from './pipes/StringifyEnum'; import {StringifySearchType} from './pipes/StringifySearchType'; import {MarkerFactory} from './ui/gallery/map/MarkerFactory'; +import {IconComponent} from './icon.component'; @Injectable() export class MyHammerConfig extends HammerGestureConfig { @@ -165,6 +166,7 @@ Marker.prototype.options.icon = MarkerFactory.defIcon; ], declarations: [ AppComponent, + IconComponent, LoginComponent, ShareLoginComponent, GalleryComponent, diff --git a/src/frontend/app/icon.component.ts b/src/frontend/app/icon.component.ts new file mode 100644 index 000000000..6bf861403 --- /dev/null +++ b/src/frontend/app/icon.component.ts @@ -0,0 +1,24 @@ +import {Component, Input} from '@angular/core'; +import {Config} from '../../common/config/public/Config'; + +@Component({ + selector: 'app-icon', + template: ` + + + `, +}) +export class IconComponent { + + @Input() width: number; + @Input() height: number; + + protected readonly Config = Config; + + constructor() { + } +} diff --git a/src/frontend/app/ui/albums/album/album.component.css b/src/frontend/app/ui/albums/album/album.component.css index e856eea55..fb4204e96 100644 --- a/src/frontend/app/ui/albums/album/album.component.css +++ b/src/frontend/app/ui/albums/album/album.component.css @@ -35,11 +35,13 @@ a { } .no-image { - position: absolute; - color: #7f7f7f; - font-size: 80px; - top: calc(50% - 40px); - left: calc(50% - 40px); + display: block; + color: var(--bs-secondary-color); + width: 100px; + top: calc(50%); + left: calc(50%); + transform: translate(-50%, -50%); + position: relative; } .photo { diff --git a/src/frontend/app/ui/albums/album/album.component.html b/src/frontend/app/ui/albums/album/album.component.html index 8ebb96fa8..82b4490d6 100644 --- a/src/frontend/app/ui/albums/album/album.component.html +++ b/src/frontend/app/ui/albums/album/album.component.html @@ -10,9 +10,7 @@ *ngIf="thumbnail && thumbnail.Available" [style.background-image]="getSanitizedThUrl()"> - + diff --git a/src/frontend/app/ui/albums/album/album.component.ts b/src/frontend/app/ui/albums/album/album.component.ts index ce5a10dd5..e503faec3 100644 --- a/src/frontend/app/ui/albums/album/album.component.ts +++ b/src/frontend/app/ui/albums/album/album.component.ts @@ -11,6 +11,7 @@ import { AlbumBaseDTO } from '../../../../../common/entities/album/AlbumBaseDTO' import { Media } from '../../gallery/Media'; import { SavedSearchDTO } from '../../../../../common/entities/album/SavedSearchDTO'; import { UserRoles } from '../../../../../common/entities/UserDTO'; +import {Config} from '../../../../../common/config/public/Config'; @Component({ selector: 'app-album', @@ -21,6 +22,7 @@ import { UserRoles } from '../../../../../common/entities/UserDTO'; export class AlbumComponent implements OnInit, OnDestroy { @Input() album: AlbumBaseDTO; @Input() size: number; + public readonly svgIcon = Config.Server.svgIcon; public thumbnail: Thumbnail = null; diff --git a/src/frontend/app/ui/albums/albums.service.ts b/src/frontend/app/ui/albums/albums.service.ts index 8b3e86077..5d303610f 100644 --- a/src/frontend/app/ui/albums/albums.service.ts +++ b/src/frontend/app/ui/albums/albums.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@angular/core'; -import { NetworkService } from '../../model/network/network.service'; -import { BehaviorSubject } from 'rxjs'; -import { AlbumBaseDTO } from '../../../../common/entities/album/AlbumBaseDTO'; -import { SearchQueryDTO } from '../../../../common/entities/SearchQueryDTO'; +import {Injectable} from '@angular/core'; +import {NetworkService} from '../../model/network/network.service'; +import {BehaviorSubject} from 'rxjs'; +import {AlbumBaseDTO} from '../../../../common/entities/album/AlbumBaseDTO'; +import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; @Injectable() export class AlbumsService { diff --git a/src/frontend/app/ui/frame/frame.component.html b/src/frontend/app/ui/frame/frame.component.html index 119c4ac0c..3107671f2 100644 --- a/src/frontend/app/ui/frame/frame.component.html +++ b/src/frontend/app/ui/frame/frame.component.html @@ -7,7 +7,7 @@
- + {{title}} - +
diff --git a/src/frontend/app/ui/gallery/directories/directory/directory.gallery.component.ts b/src/frontend/app/ui/gallery/directories/directory/directory.gallery.component.ts index 5c42d53eb..a4773a31c 100644 --- a/src/frontend/app/ui/gallery/directories/directory/directory.gallery.component.ts +++ b/src/frontend/app/ui/gallery/directories/directory/directory.gallery.component.ts @@ -1,15 +1,13 @@ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; -import { SubDirectoryDTO } from '../../../../../../common/entities/DirectoryDTO'; -import { RouterLink } from '@angular/router'; -import { Utils } from '../../../../../../common/Utils'; -import { Media } from '../../Media'; -import { - Thumbnail, - ThumbnailManagerService, -} from '../../thumbnailManager.service'; -import { QueryService } from '../../../../model/query.service'; -import { PreviewPhotoDTO } from '../../../../../../common/entities/PhotoDTO'; +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {DomSanitizer, SafeStyle} from '@angular/platform-browser'; +import {SubDirectoryDTO} from '../../../../../../common/entities/DirectoryDTO'; +import {RouterLink} from '@angular/router'; +import {Utils} from '../../../../../../common/Utils'; +import {Media} from '../../Media'; +import {Thumbnail, ThumbnailManagerService,} from '../../thumbnailManager.service'; +import {QueryService} from '../../../../model/query.service'; +import {PreviewPhotoDTO} from '../../../../../../common/entities/PhotoDTO'; +import {Config} from '../../../../../../common/config/public/Config'; @Component({ selector: 'app-gallery-directory', @@ -26,7 +24,8 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy { private thumbnailService: ThumbnailManagerService, private sanitizer: DomSanitizer, public queryService: QueryService - ) {} + ) { + } public get SamplePhoto(): PreviewPhotoDTO { return this.directory.preview; @@ -35,10 +34,10 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy { getSanitizedThUrl(): SafeStyle { return this.sanitizer.bypassSecurityTrustStyle( 'url(' + - this.thumbnail.Src.replace(/\(/g, '%28') - .replace(/'/g, '%27') - .replace(/\)/g, '%29') + - ')' + this.thumbnail.Src.replace(/\(/g, '%28') + .replace(/'/g, '%27') + .replace(/\)/g, '%29') + + ')' ); } diff --git a/src/frontend/app/ui/login/login.component.css b/src/frontend/app/ui/login/login.component.css index 1e9a19a84..89b42824d 100644 --- a/src/frontend/app/ui/login/login.component.css +++ b/src/frontend/app/ui/login/login.component.css @@ -4,9 +4,6 @@ text-align: center; } -.title img { - height: 80px; -} .card { diff --git a/src/frontend/app/ui/login/login.component.html b/src/frontend/app/ui/login/login.component.html index d24f3056d..a8798f4f2 100644 --- a/src/frontend/app/ui/login/login.component.html +++ b/src/frontend/app/ui/login/login.component.html @@ -3,7 +3,9 @@
-

{{title}}

+

+ + {{title}}

diff --git a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html index b676d1ede..a5e8adfe6 100644 --- a/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html +++ b/src/frontend/app/ui/settings/template/settings-entry/settings-entry.component.html @@ -157,7 +157,7 @@ (click)="showIconModal(iconModalTmp)"> @@ -176,7 +176,7 @@
diff --git a/src/frontend/app/ui/settings/template/template.component.html b/src/frontend/app/ui/settings/template/template.component.html index d14ef645c..116d39b47 100644 --- a/src/frontend/app/ui/settings/template/template.component.html +++ b/src/frontend/app/ui/settings/template/template.component.html @@ -79,11 +79,11 @@
- +
!(this.states.value.__state[key].shouldHide && this.states.value.__state[key].shouldHide()) - }); - } + for (const key of this.getKeys(this.states)) { + if (this.states.value.__state[key].isConfigType && + this.states?.value.__state[key].tags?.uiIcon) { + this.nestedConfigs.push({ + id: this.ConfigPath + '.' + key, + name: this.states?.value.__state[key].tags?.name, + icon: this.states?.value.__state[key].tags?.uiIcon, + visible: () => !(this.states.value.__state[key].shouldHide && this.states.value.__state[key].shouldHide()) + }); } + } } @@ -270,6 +270,10 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting this.getSettings(); } + isExpandableConfig(c: ConfigState) { + return c.isConfigType && c.tags?.uiType !== 'SVGIconConfig'; + } + public async save(): Promise { this.inProgress = true; @@ -306,8 +310,8 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting } const s = states.value.__state; const keys = Object.keys(s).sort((a, b) => { - if ((s[a].isConfigType || s[a].isConfigArrayType) !== (s[b].isConfigType || s[b].isConfigArrayType)) { - if (s[a].isConfigType || s[a].isConfigArrayType) { + if ((this.isExpandableConfig(s[a]) || s[a].isConfigArrayType) !== (this.isExpandableConfig(s[b]) || s[b].isConfigArrayType)) { + if (this.isExpandableConfig(s[a]) || s[a].isConfigArrayType) { return 1; } else { return -1; diff --git a/src/frontend/app/ui/sharelogin/share-login.component.css b/src/frontend/app/ui/sharelogin/share-login.component.css index a3e3ef1c4..0f8123447 100644 --- a/src/frontend/app/ui/sharelogin/share-login.component.css +++ b/src/frontend/app/ui/sharelogin/share-login.component.css @@ -4,9 +4,6 @@ text-align: center; } -.title img { - height: 80px; -} @media screen and ( max-width: 500px ) { .title h1 { diff --git a/src/frontend/app/ui/sharelogin/share-login.component.html b/src/frontend/app/ui/sharelogin/share-login.component.html index 831b5c249..a43f5cb34 100644 --- a/src/frontend/app/ui/sharelogin/share-login.component.html +++ b/src/frontend/app/ui/sharelogin/share-login.component.html @@ -3,12 +3,13 @@
-

{{title}}

+

{{title}}

-
Unknown sharing key.
+
Unknown sharing key. +
Wrong password
diff --git a/src/frontend/assets/icon.png b/src/frontend/assets/icon.png deleted file mode 100644 index 2bca66f23..000000000 Binary files a/src/frontend/assets/icon.png and /dev/null differ diff --git a/src/frontend/assets/icon_inv.png b/src/frontend/assets/icon_inv.png deleted file mode 100644 index 32d122dc2..000000000 Binary files a/src/frontend/assets/icon_inv.png and /dev/null differ diff --git a/src/frontend/index.html b/src/frontend/index.html index 9ef0b839d..3b177d06d 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -4,14 +4,16 @@ Loading.. - + + - + + @@ -33,8 +35,14 @@ -webkit-transform: translate(-50%, -50%); left: 50%; text-align: center"> - -

Loading...

+ + + +

Loading...