mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-12-23 01:27:14 +02:00
parent
868dd6433e
commit
f9bd62db3f
@ -101,6 +101,8 @@ 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 || '';
|
||||
res.tpl.usedTheme = selectedTheme;
|
||||
|
||||
return next();
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ export type TAGS = {
|
||||
experimental?: boolean, //is it a beta feature
|
||||
unit?: string, // Unit info to display on UI
|
||||
uiIcon?: string,
|
||||
uiType?: 'SearchQuery', // Hint for the UI about the type
|
||||
uiType?: 'SearchQuery' | 'ThemeSelector' | 'SelectedThemeSettings', // Hint for the UI about the type
|
||||
uiOptions?: (string | number)[], //Hint for the UI about the recommended options
|
||||
uiAllowSpaces?: boolean
|
||||
uiOptional?: boolean; //makes the tag not "required"
|
||||
@ -502,6 +502,30 @@ export class ClientLightboxConfig {
|
||||
loopVideos: boolean = false;
|
||||
}
|
||||
|
||||
@SubConfigClass<TAGS>({tags: {client: true}, softReadonly: true})
|
||||
export class ThemeConfig {
|
||||
|
||||
constructor(name?: string, theme?: string) {
|
||||
this.name = name;
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`Name`,
|
||||
} as TAGS,
|
||||
description: $localize`Name of the theme`
|
||||
})
|
||||
name: string;
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`Theme`,
|
||||
} as TAGS,
|
||||
description: $localize`Adds these css settings as it is to the end of the body tag of the page.`
|
||||
})
|
||||
theme: string;
|
||||
}
|
||||
|
||||
@SubConfigClass<TAGS>({tags: {client: true}, softReadonly: true})
|
||||
export class ThemesConfig {
|
||||
|
||||
@ -524,6 +548,42 @@ export class ThemesConfig {
|
||||
description: $localize`Sets the default theme mode that is used for the application.`
|
||||
})
|
||||
defaultMode: ThemeModes = ThemeModes.light;
|
||||
|
||||
@ConfigProperty({
|
||||
type: 'string',
|
||||
tags: {
|
||||
name: $localize`Selected theme`,
|
||||
uiDisabled: (sb: ThemesConfig) => !sb.enabled,
|
||||
uiType: 'ThemeSelector'
|
||||
} as TAGS,
|
||||
description: $localize`Selected theme to use on the site.`
|
||||
})
|
||||
selectedTheme: 'default' | string = 'classic';
|
||||
|
||||
@ConfigProperty({
|
||||
arrayType: ThemeConfig,
|
||||
tags: {
|
||||
name: $localize`Selected theme css`, //this is a 'hack' to the UI settings. UI will only show the selected setting's css
|
||||
uiDisabled: (sb: ThemesConfig) => !sb.enabled,
|
||||
relevant: (c: ThemesConfig) => c.selectedTheme !== 'default',
|
||||
uiType: 'SelectedThemeSettings'
|
||||
} as TAGS,
|
||||
description: $localize`Adds these css settings as it is to the end of the body tag of the page.`
|
||||
})
|
||||
availableThemes: ThemeConfig[] = [
|
||||
new ThemeConfig(
|
||||
'classic',
|
||||
':root nav.navbar {\n' +
|
||||
'--bs-navbar-color: rgba(255, 255, 255, 0.55);\n' +
|
||||
'--bs-navbar-hover-color: rgba(255, 255, 255, 0.75);\n' +
|
||||
'--bs-navbar-disabled-color: rgba(255, 255, 255, 0.25);\n' +
|
||||
'--bs-navbar-active-color: #fff;\n' +
|
||||
'--bs-navbar-brand-color: #fff;\n' +
|
||||
'--bs-navbar-brand-hover-color: #fff;\n' +
|
||||
'--bs-bg-opacity: 1;\n' +
|
||||
'background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;\n' +
|
||||
'}'
|
||||
)];
|
||||
}
|
||||
|
||||
|
||||
@ -629,7 +689,8 @@ export class ClientGalleryConfig {
|
||||
tags: {
|
||||
name: $localize`Themes`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
} as TAGS,
|
||||
description:$localize`Pigallery2 uses Bootstrap 5.3 (https://getbootstrap.com/docs/5.3) for design (css, layout). In dark mode it sets 'data-bs-theme="dark"' to the <html> to take advantage bootstrap's color modes. For theming, read more at: https://getbootstrap.com/docs/5.3/customize/color-modes/`
|
||||
})
|
||||
Themes: ThemesConfig = new ThemesConfig();
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
import {ClientConfig} from './ClientConfig';
|
||||
import {WebConfigClass} from 'typeconfig/src/decorators/class/WebConfigClass';
|
||||
import {WebConfigClassBuilder} from 'typeconfig/src/decorators/builders/WebConfigClassBuilder';
|
||||
import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty';
|
||||
import {IWebConfigClass} from 'typeconfig/common';
|
||||
|
||||
/**
|
||||
* These configuration will be available at frontend and backend too
|
||||
*/
|
||||
@WebConfigClass()
|
||||
export class ClientClass extends ClientConfig{
|
||||
export class ClientClass extends ClientConfig {
|
||||
}
|
||||
|
||||
// ConfigInject is getting injected form the server side to the global scope
|
||||
|
@ -3,7 +3,7 @@
|
||||
[class.navbar-keep-top]="navbarKeepTop"
|
||||
[class.hide-navbar]="!navbarKeepTop && shouldHideNavbar"
|
||||
[class.animate-navbar]="animateNavbar">
|
||||
<nav class="navbar navbar-expand navbar-dark bg-dark">
|
||||
<nav class="navbar navbar-expand">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand d-none d-sm-block" [routerLink]="['/gallery']"
|
||||
[queryParams]="queryService.getParams()">
|
||||
|
@ -61,7 +61,7 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template #saveSearchModal>
|
||||
<!-- sharing Modal-->
|
||||
<!-- search Modal-->
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" i18n>Save search to album</h5>
|
||||
<button type="button" class="btn-close" (click)="hideSaveSearchModal()" data-dismiss="modal" aria-label="Close">
|
||||
|
@ -53,9 +53,9 @@
|
||||
</select>
|
||||
|
||||
|
||||
<div class="form-check form-switch fs-5">
|
||||
<div class="form-check form-switch fs-5"
|
||||
*ngSwitchCase="'Boolean'">
|
||||
<input class="form-check-input"
|
||||
*ngSwitchCase="'Boolean'"
|
||||
[id]="idName"
|
||||
[name]="idName"
|
||||
[title]="title"
|
||||
@ -66,6 +66,82 @@
|
||||
[(ngModel)]="state.value">
|
||||
</div>
|
||||
|
||||
<div class="input-group"
|
||||
*ngSwitchCase="'ThemeSelector'">
|
||||
<select
|
||||
[id]="idName"
|
||||
[name]="idName"
|
||||
[title]="title"
|
||||
(ngModelChange)="onChange($event)"
|
||||
[disabled]="state.readonly || Disabled"
|
||||
class="form-select" [(ngModel)]="state.value">
|
||||
<option *ngFor="let th of AvailableThemes" [ngValue]="th.key">{{th.value}}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<button class="btn btn-primary"
|
||||
(click)="showNewThemeModal(newThemeModal)">
|
||||
<span class="oi oi-plus me-2"></span>
|
||||
<ng-container i18n>Add new</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-danger"
|
||||
*ngIf="state.value !== 'default'"
|
||||
(click)="removeTheme()">
|
||||
<span class="oi oi-trash"></span>
|
||||
</button>
|
||||
|
||||
<ng-template #newThemeModal>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" i18n>Add new theme</h5>
|
||||
<button type="button" class="btn-close" (click)="hideNewThemeModal()" data-dismiss="modal"
|
||||
aria-label="Close">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<label for="themeName" i18n>Theme name</label>
|
||||
<input
|
||||
id="themeName"
|
||||
name="themeName"
|
||||
placeholder="name"
|
||||
i18n-placeholder
|
||||
class="form-control input-md"
|
||||
required="required"
|
||||
[(ngModel)]="newThemeName"
|
||||
type="text"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
|
||||
<div class="btn-group float-end row mt-2" role="group" style="display: block">
|
||||
<div class="pe-0">
|
||||
<button class="btn btn-primary" type="button"
|
||||
(click)="addNewTheme()">
|
||||
<span class="oi oi-plus me-2"></span>
|
||||
<ng-container i18n>Add new Theme</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
rows="5"
|
||||
*ngSwitchCase="'SelectedThemeSettings'"
|
||||
type="text"
|
||||
class="form-control"
|
||||
[title]="title"
|
||||
[id]="idName"
|
||||
[(ngModel)]="SelectedThemeSettings.theme"
|
||||
(ngModelChange)="onChange($event)"
|
||||
[name]="idName"
|
||||
[disabled]="state.readonly || Disabled"
|
||||
required="required">
|
||||
</textarea>
|
||||
|
||||
|
||||
<app-settings-workflow
|
||||
class="w-100"
|
||||
*ngSwitchCase="'JobScheduleConfig'"
|
||||
@ -231,7 +307,7 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row me-0" >
|
||||
<div class="row me-0">
|
||||
<div class="col pe-0">
|
||||
<button class="btn btn-primary float-end"
|
||||
[disabled]="state.readonly"
|
||||
@ -285,7 +361,7 @@
|
||||
triggers="mouseenter:mouseleave"
|
||||
placement="bottom"
|
||||
[popover]="popTemplate"
|
||||
class="oi oi-warning text-warning warning-icon ms-2" ></span>
|
||||
class="oi oi-warning text-warning warning-icon ms-2"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
import {Component, forwardRef, OnChanges} from '@angular/core';
|
||||
import {Component, forwardRef, OnChanges, TemplateRef} from '@angular/core';
|
||||
import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator,} from '@angular/forms';
|
||||
import {Utils} from '../../../../../../common/Utils';
|
||||
import {propertyTypes} from 'typeconfig/common';
|
||||
import {SearchQueryParserService} from '../../../gallery/search/search-query-parser.service';
|
||||
import {MapLayers, NavigationLinkConfig, NavigationLinkTypes, TAGS} from '../../../../../../common/config/public/ClientConfig';
|
||||
import {MapLayers, NavigationLinkConfig, NavigationLinkTypes, TAGS, ThemeConfig} from '../../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../../settings.service';
|
||||
import {WebConfig} from '../../../../../../common/config/private/WebConfig';
|
||||
import {JobScheduleConfig, UserConfig} from '../../../../../../common/config/private/PrivateConfig';
|
||||
import {enumToTranslatedArray} from '../../../EnumTranslations';
|
||||
import {BsModalService} from '../../../../../../../node_modules/ngx-bootstrap/modal';
|
||||
|
||||
interface IState {
|
||||
shouldHide(): boolean;
|
||||
@ -67,10 +68,12 @@ export class SettingsEntryComponent
|
||||
public type: string | object;
|
||||
public arrayType: string;
|
||||
public uiType: string;
|
||||
newThemeModalRef: any;
|
||||
|
||||
|
||||
constructor(private searchQueryParserService: SearchQueryParserService,
|
||||
public settingsService: SettingsService,
|
||||
private modalService: BsModalService,
|
||||
) {
|
||||
}
|
||||
|
||||
@ -171,6 +174,18 @@ export class SettingsEntryComponent
|
||||
|
||||
}
|
||||
|
||||
get AvailableThemes() {
|
||||
return [{
|
||||
key: 'default',
|
||||
value: $localize`default`
|
||||
}, ...(this.state.rootConfig as any).__state.availableThemes.value
|
||||
.map((th: ThemeConfig) => ({key: th.name, value: th.name}))];
|
||||
}
|
||||
|
||||
|
||||
get SelectedThemeSettings(): { theme: string } {
|
||||
return (this.state.value as ThemeConfig[]).find(th => th.name === (this.state.rootConfig as any).__state.selectedTheme.value) || {theme: 'N/A'};
|
||||
}
|
||||
|
||||
get Disabled() {
|
||||
if (!this.state?.tags?.uiDisabled) {
|
||||
@ -209,8 +224,8 @@ export class SettingsEntryComponent
|
||||
this.arrayType !== 'UserConfig') {
|
||||
this.uiType = 'StringInput';
|
||||
}
|
||||
if (this.type === 'SearchQuery') {
|
||||
this.uiType = 'SearchQuery';
|
||||
if (this.type === this.state.tags?.uiType) {
|
||||
this.uiType = this.state.tags?.uiType;
|
||||
} else if (this.state.isEnumType) {
|
||||
this.uiType = 'EnumType';
|
||||
} else if (this.type === 'boolean') {
|
||||
@ -301,6 +316,7 @@ export class SettingsEntryComponent
|
||||
public onTouched = (): void => {
|
||||
// empty
|
||||
};
|
||||
newThemeName: string;
|
||||
|
||||
public writeValue(obj: IState): void {
|
||||
this.state = obj;
|
||||
@ -324,7 +340,6 @@ export class SettingsEntryComponent
|
||||
}
|
||||
this.state.value.push(this.state.value[this.state.value.length - 1]);
|
||||
}
|
||||
console.dir(this.state.value);
|
||||
}
|
||||
|
||||
remove(i: number): void {
|
||||
@ -350,6 +365,44 @@ export class SettingsEntryComponent
|
||||
}
|
||||
|
||||
|
||||
addNewTheme(): void {
|
||||
const availableThemes = (this.state.rootConfig as any).__state.availableThemes;
|
||||
if (!this.newThemeName ||
|
||||
(availableThemes.value as ThemeConfig[]).find(th => th.name === this.newThemeName)) {
|
||||
return;
|
||||
}
|
||||
this.state.value = this.newThemeName;
|
||||
availableThemes.value.push(new availableThemes.arrayType(this.newThemeName, ''));
|
||||
this.newThemeName = '';
|
||||
this.onChange(null);
|
||||
this.hideNewThemeModal();
|
||||
}
|
||||
|
||||
|
||||
removeTheme(): void {
|
||||
const availableThemes = (this.state.rootConfig as any).__state.availableThemes;
|
||||
const i = (availableThemes.value as ThemeConfig[]).findIndex(th => th.name === this.state.value);
|
||||
if (i >= 0) {
|
||||
this.state.value = 'default';
|
||||
availableThemes.value.splice(i, 1);
|
||||
this.onChange(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
showNewThemeModal(template: TemplateRef<any>): void {
|
||||
this.newThemeModalRef = this.modalService.show(template, {
|
||||
class: 'modal-lg',
|
||||
});
|
||||
document.body.style.paddingRight = '0px';
|
||||
}
|
||||
|
||||
|
||||
public hideNewThemeModal(): void {
|
||||
this.newThemeModalRef.hide();
|
||||
this.newThemeModalRef = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -37,5 +37,9 @@
|
||||
<h2>Loading...</h2>
|
||||
</div>
|
||||
</app-pi-gallery2>
|
||||
|
||||
<style>
|
||||
<%- usedTheme %>
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user