setting segments separation
clear separation between setting segments by adding a background
This commit is contained in:
parent
45c029a50d
commit
4a3fd8a089
12 changed files with 1466 additions and 1361 deletions
|
|
@ -10,145 +10,147 @@
|
|||
<form [formGroup]="settingsForm">
|
||||
<p class="alert alert-warning">{{t('setting-description')}} {{t('test-warning')}}</p>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('hostName'); as formControl) {
|
||||
<app-setting-item [title]="t('host-name-label')" [subtitle]="t('host-name-tooltip')" [control]="formControl">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-hostname" aria-describedby="hostname-validations" class="form-control" formControlName="hostName" type="text"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if (formControl.errors; as errors) {
|
||||
<div id="hostname-validations" class="invalid-feedback" style="display: inline-block">
|
||||
@if (errors.pattern) {
|
||||
<div>{{t('host-name-validation')}}</div>
|
||||
}
|
||||
<div class="section">
|
||||
<div class="row g-0">
|
||||
@if (settingsForm.get('hostName'); as formControl) {
|
||||
<app-setting-item [title]="t('host-name-label')" [subtitle]="t('host-name-tooltip')" [control]="formControl">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-hostname" aria-describedby="hostname-validations" class="form-control" formControlName="hostName" type="text"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if (formControl.errors; as errors) {
|
||||
<div id="hostname-validations" class="invalid-feedback" style="display: inline-block">
|
||||
@if (errors.pattern) {
|
||||
<div>{{t('host-name-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('senderAddress'); as formControl) {
|
||||
<app-setting-item [title]="t('sender-address-label')" [subtitle]="t('sender-address-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="text" class="form-control" formControlName="senderAddress" id="settings-sender-address" appEnterBlur/>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('senderDisplayName'); as formControl) {
|
||||
<app-setting-item [title]="t('sender-displayname-label')" [subtitle]="t('sender-displayname-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="text" class="form-control" formControlName="senderDisplayName" id="settings-sender-displayname" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('host'); as formControl) {
|
||||
<app-setting-item [title]="t('host-label')" [subtitle]="t('host-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" formControlName="host" id="settings-host" appEnterBlur />
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="autofillGmail()">{{t('gmail-label')}}</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="autofillOutlook()">{{t('outlook-label')}}</button>
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('senderAddress'); as formControl) {
|
||||
<app-setting-item [title]="t('sender-address-label')" [subtitle]="t('sender-address-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="text" class="form-control" formControlName="senderAddress" id="settings-sender-address" appEnterBlur/>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('senderDisplayName'); as formControl) {
|
||||
<app-setting-item [title]="t('sender-displayname-label')" [subtitle]="t('sender-displayname-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="text" class="form-control" formControlName="senderDisplayName" id="settings-sender-displayname" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('host'); as formControl) {
|
||||
<app-setting-item [title]="t('host-label')" [subtitle]="t('host-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" formControlName="host" id="settings-host" appEnterBlur />
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="autofillGmail()">{{t('gmail-label')}}</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="autofillOutlook()">{{t('outlook-label')}}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('port'); as formControl) {
|
||||
<app-setting-item [title]="t('port-label')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="number" inputmode="numeric" min="1" class="form-control" formControlName="port" id="settings-port" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('enableSsl'); as formControl) {
|
||||
<app-setting-switch labelId="setting-enable-ssl" [title]="t('enable-ssl-label')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="setting-enable-ssl" type="checkbox" class="form-check-input" formControlName="enableSsl">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('userName'); as formControl) {
|
||||
<app-setting-item [title]="t('username-label')" [subtitle]="t('username-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="text" class="form-control" formControlName="userName" id="settings-username" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('password'); as formControl) {
|
||||
<app-setting-item [title]="t('password-label')">
|
||||
<ng-template #view>
|
||||
{{formControl.value ? '********' : null | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="text" class="form-control" formControlName="password" id="settings-password" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('sizeLimit'); as formControl) {
|
||||
<app-setting-item [title]="t('size-limit-label')" [subtitle]="t('size-limit-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | bytes}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="number" inputmode="numeric" min="1" class="form-control" formControlName="sizeLimit" id="settings-size-limit" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('customizedTemplates'); as formControl) {
|
||||
<app-setting-switch [title]="t('customized-templates-label')" [subtitle]="t('customized-templates-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="settings-customized-templates" type="checkbox" class="form-check-input" formControlName="customizedTemplates">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('port'); as formControl) {
|
||||
<app-setting-item [title]="t('port-label')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="number" inputmode="numeric" min="1" class="form-control" formControlName="port" id="settings-port" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('enableSsl'); as formControl) {
|
||||
<app-setting-switch labelId="setting-enable-ssl" [title]="t('enable-ssl-label')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="setting-enable-ssl" type="checkbox" class="form-check-input" formControlName="enableSsl">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('userName'); as formControl) {
|
||||
<app-setting-item [title]="t('username-label')" [subtitle]="t('username-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="text" class="form-control" formControlName="userName" id="settings-username" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('password'); as formControl) {
|
||||
<app-setting-item [title]="t('password-label')">
|
||||
<ng-template #view>
|
||||
{{formControl.value ? '********' : null | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="text" class="form-control" formControlName="password" id="settings-password" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if (settingsForm.get('sizeLimit'); as formControl) {
|
||||
<app-setting-item [title]="t('size-limit-label')" [subtitle]="t('size-limit-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | bytes}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input type="number" inputmode="numeric" min="1" class="form-control" formControlName="sizeLimit" id="settings-size-limit" appEnterBlur />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('customizedTemplates'); as formControl) {
|
||||
<app-setting-switch [title]="t('customized-templates-label')" [subtitle]="t('customized-templates-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="settings-customized-templates" type="checkbox" class="form-check-input" formControlName="customizedTemplates">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,60 +9,62 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('encodeMediaAs'); as formControl) {
|
||||
<app-setting-item [title]="t('encode-as-label')" [subtitle]="t('encode-as-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | encodeFormat}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" formControlName="encodeMediaAs">
|
||||
@for (opt of allEncodeFormats; track opt) {
|
||||
<option [value]="opt">{{opt | encodeFormat}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('coverImageSize'); as formControl) {
|
||||
<app-setting-item [title]="t('cover-image-size-label')" [subtitle]="t('cover-image-size-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl!.value | coverImageSize}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" formControlName="coverImageSize">
|
||||
@for (opt of allCoverImageSizes; track opt) {
|
||||
<option [value]="opt">{{opt | coverImageSize}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('bookmarksDirectory'); as formControl) {
|
||||
<app-setting-item [title]="t('bookmark-dir-label')" [subtitle]="t('bookmark-dir-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl!.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="input-group">
|
||||
<input readonly id="settings-bookmarksdir" class="form-control" formControlName="bookmarksDirectory" type="text" aria-describedby="change-bookmarks-dir">
|
||||
<button id="change-bookmarks-dir" class="btn btn-primary" (click)="openDirectoryChooser(formControl.value, 'bookmarksDirectory')">
|
||||
{{t('change')}}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
<div class="section">
|
||||
<ng-container>
|
||||
<div class="row g-0">
|
||||
@if(settingsForm.get('encodeMediaAs'); as formControl) {
|
||||
<app-setting-item [title]="t('encode-as-label')" [subtitle]="t('encode-as-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | encodeFormat}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" formControlName="encodeMediaAs">
|
||||
@for (opt of allEncodeFormats; track opt) {
|
||||
<option [value]="opt">{{opt | encodeFormat}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('coverImageSize'); as formControl) {
|
||||
<app-setting-item [title]="t('cover-image-size-label')" [subtitle]="t('cover-image-size-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl!.value | coverImageSize}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" formControlName="coverImageSize">
|
||||
@for (opt of allCoverImageSizes; track opt) {
|
||||
<option [value]="opt">{{opt | coverImageSize}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
@if(settingsForm.get('bookmarksDirectory'); as formControl) {
|
||||
<app-setting-item [title]="t('bookmark-dir-label')" [subtitle]="t('bookmark-dir-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl!.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="input-group">
|
||||
<input readonly id="settings-bookmarksdir" class="form-control" formControlName="bookmarksDirectory" type="text" aria-describedby="change-bookmarks-dir">
|
||||
<button id="change-bookmarks-dir" class="btn btn-primary" (click)="openDirectoryChooser(formControl.value, 'bookmarksDirectory')">
|
||||
{{t('change')}}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,311 +10,314 @@
|
|||
<strong>{{t('notice')}}</strong> {{t('restart-required')}}
|
||||
</div>
|
||||
|
||||
|
||||
<h4>{{t('networking-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('hostName'); as formControl) {
|
||||
<app-setting-item [title]="t('host-name-label')" [subtitle]="t('host-name-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-hostname" aria-describedby="settings-hostname-help" class="form-control" formControlName="hostName" type="text"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="hostname-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.pattern) {
|
||||
<div>{{t('host-name-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('baseUrl'); as formControl) {
|
||||
<app-setting-item [title]="t('base-url-label')" [subtitle]="t('base-url-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="input-group">
|
||||
<input id="settings-baseurl" aria-describedby="settings-baseurl-help" class="form-control" formControlName="baseUrl" type="text"
|
||||
<div class="section">
|
||||
<h4>{{t('networking-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('hostName'); as formControl) {
|
||||
<app-setting-item [title]="t('host-name-label')" [subtitle]="t('host-name-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-hostname" aria-describedby="settings-hostname-help" class="form-control" formControlName="hostName" type="text"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="resetBaseUrl()">{{t('reset')}}</button>
|
||||
</div>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="baseurl-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.pattern) {
|
||||
<div>{{t('base-url-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('ipAddresses'); as formControl) {
|
||||
<app-setting-item [title]="t('ip-address-label')" [subtitle]="t('ip-address-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="input-group">
|
||||
<input id="settings-ipaddresses" aria-describedby="settings-ipaddresses-help" class="form-control" formControlName="ipAddresses" type="text"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="resetIPAddresses()">{{t('reset')}}</button>
|
||||
</div>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="ipaddresses-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.emptyOrPattern) {
|
||||
<div>{{t('ip-address-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('port'); as formControl) {
|
||||
<app-setting-item [title]="t('port-label')" [subtitle]="t('port-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-port" aria-describedby="settings-port-help" class="form-control"
|
||||
formControlName="port" type="number" step="1" min="1"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57" appEnterBlur>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<h4>{{t('system-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('totalBackups'); as formControl) {
|
||||
<app-setting-item [title]="t('backup-label')" [subtitle]="t('backup-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-backup" aria-describedby="total-backups-validations" class="form-control"
|
||||
formControlName="totalBackups" type="number" inputmode="numeric" step="1" min="1" max="30"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="total-backups-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.max) {
|
||||
<div>{{t('max-backup-validation', {num: formControl.errors?.max?.max})}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-backup-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('totalLogs'); as formControl) {
|
||||
<app-setting-item [title]="t('log-label')" [subtitle]="t('log-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-logs" aria-describedby="total-logs-validations" class="form-control"
|
||||
formControlName="totalLogs" type="number" inputmode="numeric" step="1" min="1" max="30"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="total-logs-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.max) {
|
||||
<div>{{t('max-logs-validation', {num: formControl.errors?.max?.max})}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-log-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('loggingLevel'); as formControl) {
|
||||
<app-setting-item [title]="t('logging-level-label')" [subtitle]="t('logging-level-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | logLevel}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
|
||||
<select id="logging-level" aria-describedby="logging-level-help" class="form-select" formControlName="loggingLevel"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched">
|
||||
@for(level of logLevels; track level) {
|
||||
<option [value]="level">{{level | logLevel}}</option>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="hostname-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.pattern) {
|
||||
<div>{{t('host-name-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</select>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="logging-level-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.pattern) {
|
||||
<div>{{t('host-name-validation')}}</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('baseUrl'); as formControl) {
|
||||
<app-setting-item [title]="t('base-url-label')" [subtitle]="t('base-url-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="input-group">
|
||||
<input id="settings-baseurl" aria-describedby="settings-baseurl-help" class="form-control" formControlName="baseUrl" type="text"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="resetBaseUrl()">{{t('reset')}}</button>
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('cacheSize'); as formControl) {
|
||||
<app-setting-item [title]="t('cache-size-label')" [subtitle]="t('cache-size-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="setting-cache-size" aria-describedby="cache-size-help" class="form-control" formControlName="cacheSize"
|
||||
type="number" inputmode="numeric" step="5" min="50" onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="cache-size-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-cache-validation')}}</div>
|
||||
}
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="baseurl-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.pattern) {
|
||||
<div>{{t('base-url-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('ipAddresses'); as formControl) {
|
||||
<app-setting-item [title]="t('ip-address-label')" [subtitle]="t('ip-address-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | defaultValue}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="input-group">
|
||||
<input id="settings-ipaddresses" aria-describedby="settings-ipaddresses-help" class="form-control" formControlName="ipAddresses" type="text"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="resetIPAddresses()">{{t('reset')}}</button>
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('enableOpds'); as formControl) {
|
||||
<app-setting-switch [title]="t('opds-label')" [subtitle]="t('opds-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="opds" type="checkbox" [attr.aria-label]="t('opds-label')" class="form-check-input" formControlName="enableOpds">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('enableFolderWatching'); as formControl) {
|
||||
<app-setting-switch [title]="t('folder-watching-label')" [subtitle]="t('folder-watching-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="folder-watching" type="checkbox" class="form-check-input" formControlName="enableFolderWatching" role="switch">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('allowStatCollection'); as formControl) {
|
||||
<app-setting-switch [title]="t('allow-stats-label')" [subtitle]="allowStatsTooltip">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="stat-collection" type="checkbox" class="form-check-input" formControlName="allowStatCollection" role="switch">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="ipaddresses-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.emptyOrPattern) {
|
||||
<div>{{t('ip-address-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('port'); as formControl) {
|
||||
<app-setting-item [title]="t('port-label')" [subtitle]="t('port-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-port" aria-describedby="settings-port-help" class="form-control"
|
||||
formControlName="port" type="number" step="1" min="1"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57" appEnterBlur>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<h4>{{t('customization-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('onDeckProgressDays'); as formControl) {
|
||||
<app-setting-item [title]="t('on-deck-last-progress-label')" [subtitle]="t('on-deck-last-progress-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="setting-on-deck-progress-days" aria-describedby="on-deck-progress-days-validations" class="form-control" formControlName="onDeckProgressDays"
|
||||
type="number" inputmode="numeric" step="1" min="1"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="on-deck-last-progress-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-days-validation')}}</div>
|
||||
<div class="section">
|
||||
<h4>{{t('system-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('totalBackups'); as formControl) {
|
||||
<app-setting-item [title]="t('backup-label')" [subtitle]="t('backup-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-backup" aria-describedby="total-backups-validations" class="form-control"
|
||||
formControlName="totalBackups" type="number" inputmode="numeric" step="1" min="1" max="30"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="total-backups-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.max) {
|
||||
<div>{{t('max-backup-validation', {num: formControl.errors?.max?.max})}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-backup-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('totalLogs'); as formControl) {
|
||||
<app-setting-item [title]="t('log-label')" [subtitle]="t('log-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="settings-logs" aria-describedby="total-logs-validations" class="form-control"
|
||||
formControlName="totalLogs" type="number" inputmode="numeric" step="1" min="1" max="30"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="total-logs-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.max) {
|
||||
<div>{{t('max-logs-validation', {num: formControl.errors?.max?.max})}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-log-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('loggingLevel'); as formControl) {
|
||||
<app-setting-item [title]="t('logging-level-label')" [subtitle]="t('logging-level-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value | logLevel}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
|
||||
<select id="logging-level" aria-describedby="logging-level-help" class="form-select" formControlName="loggingLevel"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched">
|
||||
@for(level of logLevels; track level) {
|
||||
<option [value]="level">{{level | logLevel}}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="logging-level-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.pattern) {
|
||||
<div>{{t('host-name-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('cacheSize'); as formControl) {
|
||||
<app-setting-item [title]="t('cache-size-label')" [subtitle]="t('cache-size-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="setting-cache-size" aria-describedby="cache-size-help" class="form-control" formControlName="cacheSize"
|
||||
type="number" inputmode="numeric" step="5" min="50" onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="cache-size-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-cache-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('enableOpds'); as formControl) {
|
||||
<app-setting-switch [title]="t('opds-label')" [subtitle]="t('opds-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="opds" type="checkbox" [attr.aria-label]="t('opds-label')" class="form-check-input" formControlName="enableOpds">
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('onDeckUpdateDays'); as formControl) {
|
||||
<app-setting-item [title]="t('on-deck-last-chapter-add-label')" [subtitle]="t('on-deck-last-chapter-add-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="on-deck-last-chapter-add" aria-describedby="on-deck-last-chapter-add-validations" class="form-control" formControlName="onDeckUpdateDays"
|
||||
type="number" inputmode="numeric" step="1" min="1"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="on-deck-last-chapter-add-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-days-validation')}}</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('enableFolderWatching'); as formControl) {
|
||||
<app-setting-switch [title]="t('folder-watching-label')" [subtitle]="t('folder-watching-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="folder-watching" type="checkbox" class="form-check-input" formControlName="enableFolderWatching" role="switch">
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('allowStatCollection'); as formControl) {
|
||||
<app-setting-switch [title]="t('allow-stats-label')" [subtitle]="allowStatsTooltip">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="stat-collection" type="checkbox" class="form-check-input" formControlName="allowStatCollection" role="switch">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<h4>{{t('customization-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('onDeckProgressDays'); as formControl) {
|
||||
<app-setting-item [title]="t('on-deck-last-progress-label')" [subtitle]="t('on-deck-last-progress-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="setting-on-deck-progress-days" aria-describedby="on-deck-progress-days-validations" class="form-control" formControlName="onDeckProgressDays"
|
||||
type="number" inputmode="numeric" step="1" min="1"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="on-deck-last-progress-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-days-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('onDeckUpdateDays'); as formControl) {
|
||||
<app-setting-item [title]="t('on-deck-last-chapter-add-label')" [subtitle]="t('on-deck-last-chapter-add-tooltip')">
|
||||
<ng-template #view>
|
||||
{{formControl.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input id="on-deck-last-chapter-add" aria-describedby="on-deck-last-chapter-add-validations" class="form-control" formControlName="onDeckUpdateDays"
|
||||
type="number" inputmode="numeric" step="1" min="1"
|
||||
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
|
||||
[class.is-invalid]="formControl.invalid && !formControl.untouched" appEnterBlur>
|
||||
|
||||
@if(settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="on-deck-last-chapter-add-validations" class="invalid-feedback">
|
||||
@if (formControl.errors?.required) {
|
||||
<div>{{t('field-required')}}</div>
|
||||
}
|
||||
@if (formControl.errors?.min) {
|
||||
<div>{{t('min-days-validation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,68 +1,71 @@
|
|||
<ng-container *transloco="let t; read: 'manage-system'">
|
||||
@if (serverInfo) {
|
||||
<div class="mb-3">
|
||||
<h3>{{t('title')}}</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div>{{t('version-title')}}</div>
|
||||
<div>{{serverInfo.kavitaVersion}}</div>
|
||||
<div class="section">
|
||||
<div class="mb-3">
|
||||
<h3>{{t('title')}}</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div>{{t('version-title')}}</div>
|
||||
<div>{{serverInfo.kavitaVersion}}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<div>{{t('installId-title')}}</div>
|
||||
<div>{{serverInfo.installId}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<div>{{t('installId-title')}}</div>
|
||||
<div>{{serverInfo.installId}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div>{{t('first-install-version-title')}}</div>
|
||||
<div>{{serverInfo.firstInstallVersion}}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<div>{{t('first-install-date-title')}}</div>
|
||||
<div>{{serverInfo.firstInstallDate | date:'shortDate'}}</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div>{{t('first-install-version-title')}}</div>
|
||||
<div>{{serverInfo.firstInstallVersion}}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<div>{{t('first-install-date-title')}}</div>
|
||||
<div>{{serverInfo.firstInstallDate | date:'shortDate'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
}
|
||||
|
||||
<div class="mb-3">
|
||||
<h3>{{t('more-info-title')}}</h3>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('home-page-title')}}</div>
|
||||
<div class="col"><a href="https://www.kavitareader.com" target="_blank" rel="noopener noreferrer">kavitareader.com</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('wiki-title')}}</div>
|
||||
<div class="col"><a href="https://wiki.kavitareader.com" target="_blank" rel="noopener noreferrer">wiki.kavitareader.com</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('discord-title')}}</div>
|
||||
<div class="col"><a href="https://discord.gg/b52wT37kt7" target="_blank" rel="noopener noreferrer">discord.gg/b52wT37kt7</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('donations-title')}}</div>
|
||||
<div class="col"><a href="https://opencollective.com/kavita" target="_blank" rel="noopener noreferrer">opencollective.com/kavita</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('source-title')}}</div>
|
||||
<div class="col"><a href="https://github.com/Kareadita/Kavita" target="_blank" rel="noopener noreferrer">github.com/Kareadita/Kavita</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('localization-title')}}</div>
|
||||
<div class="col"><a href="https://hosted.weblate.org/engage/kavita/" target="_blank" rel="noopener noreferrer">Weblate</a><br/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('feature-request-title')}}</div>
|
||||
<div class="col"><a href="https://github.com/Kareadita/Kavita/discussions/2529" target="_blank" rel="noopener noreferrer">https://github.com/Kareadita/Kavita/discussions/</a><br/></div>
|
||||
<div class="section">
|
||||
<div class="mb-3">
|
||||
<h3>{{t('more-info-title')}}</h3>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('home-page-title')}}</div>
|
||||
<div class="col"><a href="https://www.kavitareader.com" target="_blank" rel="noopener noreferrer">kavitareader.com</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('wiki-title')}}</div>
|
||||
<div class="col"><a href="https://wiki.kavitareader.com" target="_blank" rel="noopener noreferrer">wiki.kavitareader.com</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('discord-title')}}</div>
|
||||
<div class="col"><a href="https://discord.gg/b52wT37kt7" target="_blank" rel="noopener noreferrer">discord.gg/b52wT37kt7</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('donations-title')}}</div>
|
||||
<div class="col"><a href="https://opencollective.com/kavita" target="_blank" rel="noopener noreferrer">opencollective.com/kavita</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('source-title')}}</div>
|
||||
<div class="col"><a href="https://github.com/Kareadita/Kavita" target="_blank" rel="noopener noreferrer">github.com/Kareadita/Kavita</a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('localization-title')}}</div>
|
||||
<div class="col"><a href="https://hosted.weblate.org/engage/kavita/" target="_blank" rel="noopener noreferrer">Weblate</a><br/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">{{t('feature-request-title')}}</div>
|
||||
<div class="col"><a href="https://github.com/Kareadita/Kavita/discussions/2529" target="_blank" rel="noopener noreferrer">https://github.com/Kareadita/Kavita/discussions/</a><br/></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
|
|
|
|||
|
|
@ -1,192 +1,197 @@
|
|||
<ng-container *transloco="let t; read: 'manage-tasks-settings'">
|
||||
@if (serverSettings) {
|
||||
<form [formGroup]="settingsForm">
|
||||
|
||||
<h4>{{t('title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('taskScan'); as formControl) {
|
||||
<app-setting-item [title]="t('library-scan-label')" [subtitle]="t('library-scan-tooltip')">
|
||||
<ng-template #view>
|
||||
@if (formControl.value === customOption) {
|
||||
{{t(formControl.value)}} ({{settingsForm.get('taskScanCustom')?.value}})
|
||||
} @else {
|
||||
{{t(formControl.value)}}
|
||||
}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
|
||||
<select class="form-select" aria-describedby="settings-tasks-scan-help" formControlName="taskScan" id="settings-tasks-scan">
|
||||
@for(freq of taskFrequencies; track freq) {
|
||||
<option [value]="freq">{{t(freq)}}</option>
|
||||
<div class="section">
|
||||
<h4>{{t('title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('taskScan'); as formControl) {
|
||||
<app-setting-item [title]="t('library-scan-label')" [subtitle]="t('library-scan-tooltip')">
|
||||
<ng-template #view>
|
||||
@if (formControl.value === customOption) {
|
||||
{{t(formControl.value)}} ({{settingsForm.get('taskScanCustom')?.value}})
|
||||
} @else {
|
||||
{{t(formControl.value)}}
|
||||
}
|
||||
</select>
|
||||
|
||||
@if (formControl.value === customOption) {
|
||||
<div class="mt-3">
|
||||
<label for="custom-task-scan" class="form-label">{{t('custom-label')}}</label>
|
||||
<input class="form-control" type="text"
|
||||
id="custom-task-scan" formControlName="taskScanCustom"
|
||||
[class.is-invalid]="settingsForm.get('taskScanCustom')?.invalid && settingsForm.get('taskScanCustom')?.touched"
|
||||
aria-describedby="task-scan-validations">
|
||||
|
||||
@if (settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="task-scan-validations" class="invalid-feedback" style="display: inline-block">
|
||||
@if(settingsForm.get('taskScanCustom')?.errors?.required) {
|
||||
<div>{{t('required')}}</div>
|
||||
}
|
||||
@if(settingsForm.get('taskScanCustom')?.errors?.invalidCron) {
|
||||
<div>{{t('cron-notation')}}</div>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
|
||||
<select class="form-select" aria-describedby="settings-tasks-scan-help" formControlName="taskScan" id="settings-tasks-scan">
|
||||
@for(freq of taskFrequencies; track freq) {
|
||||
<option [value]="freq">{{t(freq)}}</option>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('taskBackup'); as formControl) {
|
||||
<app-setting-item [title]="t('library-database-backup-label')" [subtitle]="t('library-database-backup-tooltip')">
|
||||
<ng-template #view>
|
||||
@if (formControl.value === customOption) {
|
||||
{{t(formControl.value)}} ({{settingsForm.get('taskBackupCustom')?.value}})
|
||||
} @else {
|
||||
{{t(formControl.value)}}
|
||||
}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
|
||||
<select class="form-select" aria-describedby="settings-tasks-backup-help" formControlName="taskBackup" id="settings-tasks-backup">
|
||||
@for(freq of taskFrequencies; track freq) {
|
||||
<option [value]="freq">{{t(freq)}}</option>
|
||||
</select>
|
||||
|
||||
@if (formControl.value === customOption) {
|
||||
<div class="mt-3">
|
||||
<label for="custom-task-scan" class="form-label">{{t('custom-label')}}</label>
|
||||
<input class="form-control" type="text"
|
||||
id="custom-task-scan" formControlName="taskScanCustom"
|
||||
[class.is-invalid]="settingsForm.get('taskScanCustom')?.invalid && settingsForm.get('taskScanCustom')?.touched"
|
||||
aria-describedby="task-scan-validations">
|
||||
|
||||
@if (settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="task-scan-validations" class="invalid-feedback" style="display: inline-block">
|
||||
@if(settingsForm.get('taskScanCustom')?.errors?.required) {
|
||||
<div>{{t('required')}}</div>
|
||||
}
|
||||
@if(settingsForm.get('taskScanCustom')?.errors?.invalidCron) {
|
||||
<div>{{t('cron-notation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</select>
|
||||
|
||||
@if (formControl.value === customOption) {
|
||||
<div class="mt-3">
|
||||
<label for="custom-task-scan" class="form-label">{{t('custom-label')}}</label>
|
||||
<input class="form-control" type="text"
|
||||
id="custom-task-backup" formControlName="taskBackupCustom"
|
||||
[class.is-invalid]="settingsForm.get('taskBackupCustom')?.invalid && settingsForm.get('taskBackupCustom')?.touched"
|
||||
aria-describedby="task-scan-validations">
|
||||
|
||||
@if (settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="task-backup-validations" class="invalid-feedback" style="display: inline-block">
|
||||
@if(settingsForm.get('taskBackupCustom')?.errors?.required) {
|
||||
<div>{{t('required')}}</div>
|
||||
}
|
||||
@if(settingsForm.get('taskBackupCustom')?.errors?.invalidCron) {
|
||||
<div>{{t('cron-notation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('taskCleanup'); as formControl) {
|
||||
<app-setting-item [title]="t('cleanup-label')" [subtitle]="t('cleanup-tooltip')">
|
||||
<ng-template #view>
|
||||
@if (formControl.value === customOption) {
|
||||
{{t(formControl.value)}} ({{settingsForm.get('taskCleanupCustom')?.value}})
|
||||
} @else {
|
||||
{{t(formControl.value)}}
|
||||
}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
|
||||
<select class="form-select" aria-describedby="settings-tasks-cleanup-help" formControlName="taskCleanup" id="settings-tasks-cleanup">
|
||||
@for(freq of taskFrequenciesForCleanup; track freq) {
|
||||
<option [value]="freq">{{t(freq)}}</option>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('taskBackup'); as formControl) {
|
||||
<app-setting-item [title]="t('library-database-backup-label')" [subtitle]="t('library-database-backup-tooltip')">
|
||||
<ng-template #view>
|
||||
@if (formControl.value === customOption) {
|
||||
{{t(formControl.value)}} ({{settingsForm.get('taskBackupCustom')?.value}})
|
||||
} @else {
|
||||
{{t(formControl.value)}}
|
||||
}
|
||||
</select>
|
||||
|
||||
@if (formControl.value === customOption) {
|
||||
<div class="mt-3">
|
||||
<label for="custom-task-scan" class="form-label">{{t('custom-label')}}</label>
|
||||
<input class="form-control" type="text"
|
||||
id="custom-task-cleanup" formControlName="taskCleanupCustom"
|
||||
[class.is-invalid]="settingsForm.get('taskCleanupCustom')?.invalid && settingsForm.get('taskCleanupCustom')?.touched"
|
||||
aria-describedby="task-scan-validations">
|
||||
|
||||
@if (settingsForm.get('taskCleanupCustom')?.invalid) {
|
||||
<div id="task-cleanup-validations" class="invalid-feedback" style="display: inline-block">
|
||||
@if(settingsForm.get('taskCleanupCustom')?.errors?.required) {
|
||||
<div>{{t('required')}}</div>
|
||||
}
|
||||
@if(settingsForm.get('taskCleanupCustom')?.errors?.invalidCron) {
|
||||
<div>{{t('cron-notation')}}</div>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
|
||||
<select class="form-select" aria-describedby="settings-tasks-backup-help" formControlName="taskBackup" id="settings-tasks-backup">
|
||||
@for(freq of taskFrequencies; track freq) {
|
||||
<option [value]="freq">{{t(freq)}}</option>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
</select>
|
||||
|
||||
@if (formControl.value === customOption) {
|
||||
<div class="mt-3">
|
||||
<label for="custom-task-scan" class="form-label">{{t('custom-label')}}</label>
|
||||
<input class="form-control" type="text"
|
||||
id="custom-task-backup" formControlName="taskBackupCustom"
|
||||
[class.is-invalid]="settingsForm.get('taskBackupCustom')?.invalid && settingsForm.get('taskBackupCustom')?.touched"
|
||||
aria-describedby="task-scan-validations">
|
||||
|
||||
@if (settingsForm.dirty || !settingsForm.untouched) {
|
||||
<div id="task-backup-validations" class="invalid-feedback" style="display: inline-block">
|
||||
@if(settingsForm.get('taskBackupCustom')?.errors?.required) {
|
||||
<div>{{t('required')}}</div>
|
||||
}
|
||||
@if(settingsForm.get('taskBackupCustom')?.errors?.invalidCron) {
|
||||
<div>{{t('cron-notation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if (settingsForm.get('taskCleanup'); as formControl) {
|
||||
<app-setting-item [title]="t('cleanup-label')" [subtitle]="t('cleanup-tooltip')">
|
||||
<ng-template #view>
|
||||
@if (formControl.value === customOption) {
|
||||
{{t(formControl.value)}} ({{settingsForm.get('taskCleanupCustom')?.value}})
|
||||
} @else {
|
||||
{{t(formControl.value)}}
|
||||
}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
|
||||
<select class="form-select" aria-describedby="settings-tasks-cleanup-help" formControlName="taskCleanup" id="settings-tasks-cleanup">
|
||||
@for(freq of taskFrequenciesForCleanup; track freq) {
|
||||
<option [value]="freq">{{t(freq)}}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@if (formControl.value === customOption) {
|
||||
<div class="mt-3">
|
||||
<label for="custom-task-scan" class="form-label">{{t('custom-label')}}</label>
|
||||
<input class="form-control" type="text"
|
||||
id="custom-task-cleanup" formControlName="taskCleanupCustom"
|
||||
[class.is-invalid]="settingsForm.get('taskCleanupCustom')?.invalid && settingsForm.get('taskCleanupCustom')?.touched"
|
||||
aria-describedby="task-scan-validations">
|
||||
|
||||
@if (settingsForm.get('taskCleanupCustom')?.invalid) {
|
||||
<div id="task-cleanup-validations" class="invalid-feedback" style="display: inline-block">
|
||||
@if(settingsForm.get('taskCleanupCustom')?.errors?.required) {
|
||||
<div>{{t('required')}}</div>
|
||||
}
|
||||
@if(settingsForm.get('taskCleanupCustom')?.errors?.invalidCron) {
|
||||
<div>{{t('cron-notation')}}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<h4>{{t('adhoc-tasks-title')}}</h4>
|
||||
|
||||
@for(task of adhocTasks; track task.name; let idx = $index) {
|
||||
<div class="mt-4 mb-4">
|
||||
<app-setting-button [subtitle]="t(task.description)">
|
||||
<button class="btn btn-secondary btn-sm mb-2" (click)="runAdhoc(task)">{{t(task.name)}}</button>
|
||||
</app-setting-button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<h4>{{t('adhoc-tasks-title')}}</h4>
|
||||
|
||||
@for(task of adhocTasks; track task.name; let idx = $index) {
|
||||
<div class="mt-4 mb-4">
|
||||
<app-setting-button [subtitle]="t(task.description)">
|
||||
<button class="btn btn-secondary btn-sm mb-2" (click)="runAdhoc(task)">{{t(task.name)}}</button>
|
||||
</app-setting-button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<h4>{{t('recurring-tasks-title')}}</h4>
|
||||
<ngx-datatable
|
||||
class="bootstrap"
|
||||
[rows]="recurringTasks$ | async"
|
||||
[columnMode]="ColumnMode.flex"
|
||||
rowHeight="auto"
|
||||
[footerHeight]="50"
|
||||
[limit]="15"
|
||||
>
|
||||
|
||||
<ngx-datatable-column prop="title" [sortable]="true" [draggable]="false" [resizeable]="false" [flexGrow]="3">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('job-title-header')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
{{item.title | titlecase}}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
|
||||
<ngx-datatable-column prop="lastExecutionUtc" [sortable]="true" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('last-executed-header')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" let-idx="index" ngx-datatable-cell-template>
|
||||
{{item.lastExecutionUtc | utcToLocalTime | defaultValue }}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
<ngx-datatable-column prop="cron" [sortable]="false" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('cron-header')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
{{item.cron}}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
</ngx-datatable>
|
||||
<div class="section">
|
||||
<h4>{{t('recurring-tasks-title')}}</h4>
|
||||
<ngx-datatable
|
||||
class="bootstrap"
|
||||
[rows]="recurringTasks$ | async"
|
||||
[columnMode]="ColumnMode.flex"
|
||||
rowHeight="auto"
|
||||
[footerHeight]="50"
|
||||
[limit]="15"
|
||||
>
|
||||
|
||||
<ngx-datatable-column prop="title" [sortable]="true" [draggable]="false" [resizeable]="false" [flexGrow]="3">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('job-title-header')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
{{item.title | titlecase}}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
|
||||
<ngx-datatable-column prop="lastExecutionUtc" [sortable]="true" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('last-executed-header')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" let-idx="index" ngx-datatable-cell-template>
|
||||
{{item.lastExecutionUtc | utcToLocalTime | defaultValue }}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
<ngx-datatable-column prop="cron" [sortable]="false" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('cron-header')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
{{item.cron}}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
</ngx-datatable>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -19,8 +19,34 @@ h2 {
|
|||
}
|
||||
}
|
||||
|
||||
:host ::ng-deep {
|
||||
.main-container {
|
||||
.container-fluid {
|
||||
> div {
|
||||
padding-right: unset;
|
||||
.section {
|
||||
background: var(--side-nav-bg-color);
|
||||
border-radius: 5px;
|
||||
padding: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: theme.$grid-breakpoints-lg) {
|
||||
:host ::ng-deep {
|
||||
.main-container {
|
||||
padding: 0 0 0 25px;
|
||||
.container-fluid {
|
||||
padding: unset;
|
||||
> div {
|
||||
padding-right: calc(var(--bs-gutter-x) * 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
::ng-deep .content-wrapper:not(.closed) {
|
||||
.scale {
|
||||
width: 100% !important;
|
||||
|
|
@ -28,3 +54,17 @@ h2 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: theme.$grid-breakpoints-md) {
|
||||
:host ::ng-deep {
|
||||
.main-container {
|
||||
padding: 0 0 0 14px;
|
||||
.container-fluid {
|
||||
padding: unset;
|
||||
> div {
|
||||
padding-right: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +1,139 @@
|
|||
<ng-container *transloco="let t; read:'server-stats'">
|
||||
<div class="row g-0 mt-4 mb-3 d-flex justify-content-around" *ngIf="stats$ | async as stats">
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-series-label')" [clickable]="false" fontClasses="fa-solid fa-book-open" [title]="t('total-series-tooltip', {count: stats.seriesCount | number})">
|
||||
{{t('series-count', {num: stats.seriesCount | number})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container >
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-volumes-label')" [clickable]="false" fontClasses="fas fa-book" [title]="t('total-volumes-tooltip', {count: stats.volumeCount | number})">
|
||||
{{t('volume-count', {num: stats.volumeCount | number})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-files-label')" [clickable]="false" fontClasses="fa-regular fa-file" [title]="t('total-files-tooltip', {count: stats.totalFiles | number})">
|
||||
{{t('file-count', {num: stats.totalFiles | number})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-size-label')" [clickable]="false" fontClasses="fa-solid fa-scale-unbalanced" [title]="t('total-size-label')">
|
||||
{{stats.totalSize | bytes}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-genres-label')" [clickable]="true" fontClasses="fa-solid fa-tags" [title]="t('total-genres-tooltip', {count: stats.totalGenres | number})" (click)="openGenreList();$event.stopPropagation();">
|
||||
{{t('genre-count', {num: stats.totalGenres | compactNumber})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-tags-label')" [clickable]="true" fontClasses="fa-solid fa-tags" [title]="t('total-tags-tooltip', {count: stats.totalTags | number})" (click)="openTagList();$event.stopPropagation();">
|
||||
{{t('tag-count', {num: stats.totalTags | compactNumber})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-people-label')" [clickable]="true" fontClasses="fa-solid fa-user-tag" [title]="t('total-people-tooltip', {count: stats.totalPeople | number})" (click)="openPeopleList();$event.stopPropagation();">
|
||||
{{t('people-count', {num: stats.totalPeople | compactNumber})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-read-time-label')" [clickable]="false" fontClasses="fas fa-eye" [title]="t('total-read-time-tooltip', {count: stats.totalReadingTime | number})">
|
||||
{{stats.totalReadingTime | timeDuration}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="grid row g-0 pt-2 pb-2 d-flex justify-content-around">
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="releaseYears$" [title]="t('release-years-title')" [label]="t('series')"></app-stat-list>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="mostActiveUsers$" [title]="t('most-active-users-title')" [label]="t('reads')"></app-stat-list>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="mostActiveLibrary$" [title]="t('popular-libraries-title')" [label]="t('reads')"></app-stat-list>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="mostActiveSeries$" [title]="t('popular-series-title')" [image]="seriesImage" [handleClick]="openSeries">
|
||||
</app-stat-list>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="recentlyRead$" [title]="t('recently-read-title')" [image]="seriesImage" [handleClick]="openSeries"></app-stat-list>
|
||||
<div class="section">
|
||||
<div class="row g-0 d-flex justify-content-around" *ngIf="stats$ | async as stats">
|
||||
<ng-container>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('total-series-label')" [clickable]="false" fontClasses="fa-solid fa-book-open" [title]="t('total-series-tooltip', {count: stats.seriesCount | number})">
|
||||
{{t('series-count', {num: stats.seriesCount | number})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container >
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('total-volumes-label')" [clickable]="false" fontClasses="fas fa-book" [title]="t('total-volumes-tooltip', {count: stats.volumeCount | number})">
|
||||
{{t('volume-count', {num: stats.volumeCount | number})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('total-files-label')" [clickable]="false" fontClasses="fa-regular fa-file" [title]="t('total-files-tooltip', {count: stats.totalFiles | number})">
|
||||
{{t('file-count', {num: stats.totalFiles | number})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('total-size-label')" [clickable]="false" fontClasses="fa-solid fa-scale-unbalanced" [title]="t('total-size-label')">
|
||||
{{stats.totalSize | bytes}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('total-genres-label')" [clickable]="true" fontClasses="fa-solid fa-tags" [title]="t('total-genres-tooltip', {count: stats.totalGenres | number})" (click)="openGenreList();$event.stopPropagation();">
|
||||
{{t('genre-count', {num: stats.totalGenres | compactNumber})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('total-tags-label')" [clickable]="true" fontClasses="fa-solid fa-tags" [title]="t('total-tags-tooltip', {count: stats.totalTags | number})" (click)="openTagList();$event.stopPropagation();">
|
||||
{{t('tag-count', {num: stats.totalTags | compactNumber})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('total-people-label')" [clickable]="true" fontClasses="fa-solid fa-user-tag" [title]="t('total-people-tooltip', {count: stats.totalPeople | number})" (click)="openPeopleList();$event.stopPropagation();">
|
||||
{{t('people-count', {num: stats.totalPeople | compactNumber})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('total-read-time-label')" [clickable]="false" fontClasses="fas fa-eye" [title]="t('total-read-time-tooltip', {count: stats.totalReadingTime | number})">
|
||||
{{stats.totalReadingTime | timeDuration}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 pt-2 pb-2">
|
||||
<app-top-readers></app-top-readers>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="grid row g-0 pt-2 pb-2 d-flex justify-content-around">
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="releaseYears$" [title]="t('release-years-title')" [label]="t('series')"></app-stat-list>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="mostActiveUsers$" [title]="t('most-active-users-title')" [label]="t('reads')"></app-stat-list>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="mostActiveLibrary$" [title]="t('popular-libraries-title')" [label]="t('reads')"></app-stat-list>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="mostActiveSeries$" [title]="t('popular-series-title')" [image]="seriesImage" [handleClick]="openSeries">
|
||||
</app-stat-list>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<app-stat-list [data$]="recentlyRead$" [title]="t('recently-read-title')" [image]="seriesImage" [handleClick]="openSeries"></app-stat-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 pt-4 pb-2">
|
||||
<app-file-breakdown-stats></app-file-breakdown-stats>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="row g-0 pt-2 pb-2">
|
||||
<app-top-readers></app-top-readers>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 pt-4 pb-2">
|
||||
<app-publication-status-stats></app-publication-status-stats>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="row g-0 pt-4 pb-2">
|
||||
<app-file-breakdown-stats></app-file-breakdown-stats>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 pt-4 pb-2">
|
||||
<app-reading-activity [isAdmin]="true"></app-reading-activity>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="row g-0 pt-4 pb-2">
|
||||
<app-publication-status-stats></app-publication-status-stats>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 pt-4 pb-2">
|
||||
<app-day-breakdown></app-day-breakdown>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="row g-0 pt-4 pb-2">
|
||||
<app-reading-activity [isAdmin]="true"></app-reading-activity>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="row g-0 pt-4 pb-2">
|
||||
<app-day-breakdown></app-day-breakdown>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<ng-container *transloco="let t; read:'user-stats-info-cards'">
|
||||
<div class="row g-0 mt-4 mb-3 d-flex justify-content-around">
|
||||
<div class="row g-0 d-flex justify-content-around">
|
||||
<ng-container>
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('total-pages-read-label')" [clickable]="true" fontClasses="fa-regular fa-file-lines"
|
||||
|
|
|
|||
|
|
@ -1,23 +1,36 @@
|
|||
<ng-container *transloco="let t; read:'user-stats'">
|
||||
<div class="vstack gap-3" *ngIf="userId">
|
||||
|
||||
<div class="row g-0 d-flex justify-content-around">
|
||||
<ng-container *ngIf="userStats$ | async as userStats">
|
||||
<app-user-stats-info-cards [totalPagesRead]="userStats.totalPagesRead" [totalWordsRead]="userStats.totalWordsRead" [timeSpentReading]="userStats.timeSpentReading"
|
||||
[chaptersRead]="userStats.chaptersRead" [lastActive]="userStats.lastActive" [avgHoursPerWeekSpentReading]="userStats.avgHoursPerWeekSpentReading"></app-user-stats-info-cards>
|
||||
</ng-container>
|
||||
<div class="section">
|
||||
<div class="row g-0 d-flex justify-content-around">
|
||||
<ng-container *ngIf="userStats$ | async as userStats">
|
||||
<app-user-stats-info-cards [totalPagesRead]="userStats.totalPagesRead" [totalWordsRead]="userStats.totalWordsRead" [timeSpentReading]="userStats.timeSpentReading"
|
||||
[chaptersRead]="userStats.chaptersRead" [lastActive]="userStats.lastActive" [avgHoursPerWeekSpentReading]="userStats.avgHoursPerWeekSpentReading"></app-user-stats-info-cards>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 fixed-row">
|
||||
<app-reading-activity [userId]="userId" [isAdmin]="(accountService.isAdmin$ | async) || false" [individualUserMode]="true"></app-reading-activity>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="row g-0 fixed-row">
|
||||
<app-reading-activity [userId]="userId" [isAdmin]="(accountService.isAdmin$ | async) || false" [individualUserMode]="true"></app-reading-activity>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 fixed-row">
|
||||
<app-day-breakdown [userId]="userId"></app-day-breakdown>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="row g-0 fixed-row">
|
||||
<app-day-breakdown [userId]="userId"></app-day-breakdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 fixed-row">
|
||||
<app-stat-list [data$]="percentageRead$" [label]="t('read-percentage')" [title]="t('library-read-progress-title')"></app-stat-list>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<div class="section">
|
||||
<div class="row g-0 fixed-row">
|
||||
<app-stat-list [data$]="percentageRead$" [label]="t('read-percentage')" [title]="t('library-read-progress-title')"></app-stat-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,58 +7,59 @@
|
|||
</div>
|
||||
|
||||
<p>{{t('description')}}</p>
|
||||
|
||||
<ngx-datatable
|
||||
class="bootstrap"
|
||||
[rows]="devices"
|
||||
[columnMode]="ColumnMode.flex"
|
||||
rowHeight="auto"
|
||||
[footerHeight]="50"
|
||||
[limit]="15"
|
||||
>
|
||||
|
||||
<ngx-datatable-column prop="name" [sortable]="true" [draggable]="false" [resizeable]="false" [flexGrow]="3">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('name-label')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
{{ item.name }}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
|
||||
<ngx-datatable-column prop="emailAddress" [sortable]="true" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('email-label')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" let-idx="index" ngx-datatable-cell-template>
|
||||
{{ item.emailAddress }}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
<ngx-datatable-column prop="platform" [sortable]="false" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('platform-label')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
{{ item.platform | devicePlatform }}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
<ngx-datatable-column name="actions" [sortable]="false" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
<button class="btn btn-danger me-2" (click)="deleteDevice(item)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('delete')}}</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-primary" (click)="editDevice(item)">
|
||||
<i class="fa fa-pen" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('edit')}}</span>
|
||||
</button>
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
</ngx-datatable>
|
||||
<div class="section">
|
||||
<ngx-datatable
|
||||
class="bootstrap"
|
||||
[rows]="devices"
|
||||
[columnMode]="ColumnMode.flex"
|
||||
rowHeight="auto"
|
||||
[footerHeight]="50"
|
||||
[limit]="15"
|
||||
>
|
||||
|
||||
<ngx-datatable-column prop="name" [sortable]="true" [draggable]="false" [resizeable]="false" [flexGrow]="3">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('name-label')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
{{ item.name }}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
|
||||
<ngx-datatable-column prop="emailAddress" [sortable]="true" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('email-label')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" let-idx="index" ngx-datatable-cell-template>
|
||||
{{ item.emailAddress }}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
<ngx-datatable-column prop="platform" [sortable]="false" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
{{t('platform-label')}}
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
{{ item.platform | devicePlatform }}
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
|
||||
<ngx-datatable-column name="actions" [sortable]="false" [draggable]="false" [resizeable]="false" [flexGrow]="1">
|
||||
<ng-template let-column="column" ngx-datatable-header-template>
|
||||
</ng-template>
|
||||
<ng-template let-item="row" ngx-datatable-cell-template>
|
||||
<button class="btn btn-danger me-2" (click)="deleteDevice(item)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('delete')}}</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-primary" (click)="editDevice(item)">
|
||||
<i class="fa fa-pen" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('edit')}}</span>
|
||||
</button>
|
||||
</ng-template>
|
||||
</ngx-datatable-column>
|
||||
</ngx-datatable>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -5,14 +5,17 @@
|
|||
|
||||
<p>{{t('clients-opds-description')}}</p>
|
||||
|
||||
|
||||
<app-api-key [hideData]="true" [title]="t('clients-api-key-label')" [tooltipText]="t('clients-api-key-tooltip')" [showRefresh]="true"></app-api-key>
|
||||
|
||||
<div class="section">
|
||||
<app-api-key [hideData]="true" [title]="t('clients-api-key-label')" [tooltipText]="t('clients-api-key-tooltip')" [showRefresh]="true"></app-api-key>
|
||||
</div>
|
||||
|
||||
@if (opdsEnabled && opdsUrl) {
|
||||
<div class="mt-4">
|
||||
<app-api-key [title]="t('clients-opds-url-label')" [tooltipText]="t('clients-opds-url-tooltip') + ' ' + opdsUrlLink" [hideData]="true" [showRefresh]="false"
|
||||
[transform]="makeUrl"></app-api-key>
|
||||
<div class="setting-section-break"></div>
|
||||
<div class="section">
|
||||
<div>
|
||||
<app-api-key [title]="t('clients-opds-url-label')" [tooltipText]="t('clients-opds-url-tooltip') + ' ' + opdsUrlLink" [hideData]="true" [showRefresh]="false"
|
||||
[transform]="makeUrl"></app-api-key>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,493 +5,500 @@
|
|||
|
||||
@if (user) {
|
||||
<form [formGroup]="settingsForm">
|
||||
<h4>{{t('global-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('locale-label')" [subtitle]="t('locale-tooltip')" labelId="locale">
|
||||
<ng-template #view>
|
||||
{{Locale | titlecase}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="global-header" formControlName="locale" id="locale">
|
||||
@for(opt of locales; track opt.renderName) {
|
||||
<option [value]="opt.fileName">{{opt.renderName | titlecase}} ({{opt.translationCompletion | number:'1.0-1'}}%)</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('blur-unread-summaries-label')" [subtitle]="t('blur-unread-summaries-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="blur-unread-summaries"
|
||||
formControlName="blurUnreadSummaries" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('prompt-on-download-label')" [subtitle]="t('prompt-on-download-tooltip', {size: '100'})">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="prompt-on-download"
|
||||
formControlName="promptForDownloadSize" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('disable-animations-label')" [subtitle]="t('disable-animations-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="disable-animations"
|
||||
formControlName="noTransitions" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('collapse-series-relationships-label')" [subtitle]="t('collapse-series-relationships-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="collapse-series-relationships"
|
||||
formControlName="collapseSeriesRelationships" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
<app-setting-switch [title]="t('share-series-reviews-label')" [subtitle]="t('share-series-reviews-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="share-series-reviews"
|
||||
formControlName="shareReviews" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="section">
|
||||
<h4>{{t('global-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('locale-label')" [subtitle]="t('locale-tooltip')" labelId="locale">
|
||||
<ng-template #view>
|
||||
{{Locale | titlecase}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="global-header" formControlName="locale" id="locale">
|
||||
@for(opt of locales; track opt.renderName) {
|
||||
<option [value]="opt.fileName">{{opt.renderName | titlecase}} ({{opt.translationCompletion | number:'1.0-1'}}%)</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('blur-unread-summaries-label')" [subtitle]="t('blur-unread-summaries-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="blur-unread-summaries"
|
||||
formControlName="blurUnreadSummaries" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('prompt-on-download-label')" [subtitle]="t('prompt-on-download-tooltip', {size: '100'})">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="prompt-on-download"
|
||||
formControlName="promptForDownloadSize" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('disable-animations-label')" [subtitle]="t('disable-animations-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="disable-animations"
|
||||
formControlName="noTransitions" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('collapse-series-relationships-label')" [subtitle]="t('collapse-series-relationships-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="collapse-series-relationships"
|
||||
formControlName="collapseSeriesRelationships" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
<app-setting-switch [title]="t('share-series-reviews-label')" [subtitle]="t('share-series-reviews-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="share-series-reviews"
|
||||
formControlName="shareReviews" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
|
||||
@if (licenseService.hasValidLicense$ | async) {
|
||||
<h4 id="kavitaplus-heading" class="mt-3">{{t('kavitaplus-settings-title')}}</h4>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('aniListScrobblingEnabled'); as formControl) {
|
||||
<app-setting-switch [title]="t('anilist-scrobbling-label')" [subtitle]="t('anilist-scrobbling-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="setting-anilist-scrobbling" type="checkbox"
|
||||
class="form-check-input" formControlName="aniListScrobblingEnabled">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
<div class="section">
|
||||
<h4 id="kavitaplus-heading" class="mt-3">{{t('kavitaplus-settings-title')}}</h4>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('aniListScrobblingEnabled'); as formControl) {
|
||||
<app-setting-switch [title]="t('anilist-scrobbling-label')" [subtitle]="t('anilist-scrobbling-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="setting-anilist-scrobbling" type="checkbox"
|
||||
class="form-check-input" formControlName="aniListScrobblingEnabled">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('wantToReadSync'); as formControl) {
|
||||
<app-setting-switch [title]="t('want-to-read-sync-label')" [subtitle]="t('want-to-read-sync-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="setting-want-to-read-sync" type="checkbox" class="form-check-input" formControlName="wantToReadSync">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
@if(settingsForm.get('wantToReadSync'); as formControl) {
|
||||
<app-setting-switch [title]="t('want-to-read-sync-label')" [subtitle]="t('want-to-read-sync-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="setting-want-to-read-sync" type="checkbox" class="form-check-input" formControlName="wantToReadSync">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
}
|
||||
</div>
|
||||
<div class="setting-section-break"></div>
|
||||
}
|
||||
|
||||
|
||||
<h4 id="image-reader-heading" class="mt-3">{{t('image-reader-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('reading-direction-label')" [subtitle]="t('reading-direction-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('readingDirection')!.value | readingDirection}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="readingDirection">
|
||||
@for (opt of readingDirections; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | readingDirection}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('scaling-option-label')" [subtitle]="t('scaling-option-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('scalingOption')!.value | scalingOption}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="scalingOption">
|
||||
@for (opt of scalingOptions; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | scalingOption}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('page-splitting-label')" [subtitle]="t('page-splitting-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('pageSplitOption')!.value | pageSplitOption}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="pageSplitOption">
|
||||
@for (opt of pageSplitOptions; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | pageSplitOption}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('reading-mode-label')" [subtitle]="t('reading-mode-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('readerMode')!.value | readerMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="readerMode">
|
||||
@for (opt of readerModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | readerMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('layout-mode-label')" [subtitle]="t('layout-mode-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('layoutMode')!.value | layoutMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="layoutMode">
|
||||
@for (opt of layoutModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | layoutMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('background-color-label')" [subtitle]="t('background-color-tooltip')">
|
||||
<ng-template #view>
|
||||
<div class="color-box-container">
|
||||
<div class="color-box" [ngStyle]="{'background-color': user.preferences!.backgroundColor}"></div>
|
||||
<span class="hex-code">{{ user.preferences!.backgroundColor.toUpperCase() }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input [value]="user!.preferences!.backgroundColor" class="form-control"
|
||||
(colorPickerChange)="handleBackgroundColorChange($event)"
|
||||
[style.background]="user!.preferences!.backgroundColor" [cpAlphaChannel]="'disabled'"
|
||||
[(colorPicker)]="user!.preferences!.backgroundColor" />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('auto-close-menu-label')" [subtitle]="t('auto-close-menu-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch"
|
||||
formControlName="autoCloseMenu" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('show-screen-hints-label')" [subtitle]="t('show-screen-hints-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch"
|
||||
formControlName="showScreenHints" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('emulate-comic-book-label')" [subtitle]="t('emulate-comic-book-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="emulate-comic-book"
|
||||
formControlName="emulateBook" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('swipe-to-paginate-label')" [subtitle]="t('swipe-to-paginate-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="swipe-to-paginate"
|
||||
formControlName="swipeToPaginate" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('allow-auto-webtoon-reader-label')" [subtitle]="t('allow-auto-webtoon-reader-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="allow-auto-webtoon-reader"
|
||||
formControlName="allowAutomaticWebtoonReaderDetection" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="section">
|
||||
<h4 id="image-reader-heading">{{t('image-reader-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('reading-direction-label')" [subtitle]="t('reading-direction-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('readingDirection')!.value | readingDirection}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="readingDirection">
|
||||
@for (opt of readingDirections; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | readingDirection}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('scaling-option-label')" [subtitle]="t('scaling-option-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('scalingOption')!.value | scalingOption}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="scalingOption">
|
||||
@for (opt of scalingOptions; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | scalingOption}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('page-splitting-label')" [subtitle]="t('page-splitting-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('pageSplitOption')!.value | pageSplitOption}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="pageSplitOption">
|
||||
@for (opt of pageSplitOptions; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | pageSplitOption}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('reading-mode-label')" [subtitle]="t('reading-mode-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('readerMode')!.value | readerMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="readerMode">
|
||||
@for (opt of readerModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | readerMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('layout-mode-label')" [subtitle]="t('layout-mode-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('layoutMode')!.value | layoutMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="layoutMode">
|
||||
@for (opt of layoutModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | layoutMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('background-color-label')" [subtitle]="t('background-color-tooltip')">
|
||||
<ng-template #view>
|
||||
<div class="color-box-container">
|
||||
<div class="color-box" [ngStyle]="{'background-color': user.preferences!.backgroundColor}"></div>
|
||||
<span class="hex-code">{{ user.preferences!.backgroundColor.toUpperCase() }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<input [value]="user!.preferences!.backgroundColor" class="form-control"
|
||||
(colorPickerChange)="handleBackgroundColorChange($event)"
|
||||
[style.background]="user!.preferences!.backgroundColor" [cpAlphaChannel]="'disabled'"
|
||||
[(colorPicker)]="user!.preferences!.backgroundColor" />
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('auto-close-menu-label')" [subtitle]="t('auto-close-menu-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch"
|
||||
formControlName="autoCloseMenu" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('show-screen-hints-label')" [subtitle]="t('show-screen-hints-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch"
|
||||
formControlName="showScreenHints" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('emulate-comic-book-label')" [subtitle]="t('emulate-comic-book-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="emulate-comic-book"
|
||||
formControlName="emulateBook" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('swipe-to-paginate-label')" [subtitle]="t('swipe-to-paginate-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="swipe-to-paginate"
|
||||
formControlName="swipeToPaginate" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('allow-auto-webtoon-reader-label')" [subtitle]="t('allow-auto-webtoon-reader-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="allow-auto-webtoon-reader"
|
||||
formControlName="allowAutomaticWebtoonReaderDetection" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<h4 id="book-reader-heading" class="mt-3">{{t('book-reader-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('tap-to-paginate-label')" [subtitle]="t('tap-to-paginate-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="tap-to-paginate"
|
||||
formControlName="bookReaderTapToPaginate" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('immersive-mode-label')" [subtitle]="t('immersive-mode-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch"
|
||||
formControlName="bookReaderImmersiveMode" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('reading-direction-label')" [subtitle]="t('reading-direction-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderReadingDirection')!.value | readingDirection}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderReadingDirection">
|
||||
@for (opt of readingDirections; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | readingDirection}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('font-family-label')" [subtitle]="t('font-family-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderFontFamily')!.value | titlecase}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderFontFamily">
|
||||
@for (opt of fontFamilies; track opt) {
|
||||
<option [value]="opt">{{opt | titlecase}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('writing-style-label')" [subtitle]="t('writing-style-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderWritingStyle')!.value | writingStyle}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderWritingStyle">
|
||||
@for (opt of bookWritingStyles; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | writingStyle}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('layout-mode-book-label')" [subtitle]="t('layout-mode-book-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderLayoutMode')!.value | bookPageLayoutMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderLayoutMode">
|
||||
@for (opt of bookLayoutModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | bookPageLayoutMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('color-theme-book-label')" [subtitle]="t('color-theme-book-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderThemeName')!.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderThemeName">
|
||||
@for (opt of bookColorThemesTranslated; track opt) {
|
||||
<option [value]="opt.name">{{opt.name | titlecase}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('font-size-book-label')" [subtitle]="t('font-size-book-tooltip')">
|
||||
<ng-template #view>
|
||||
<span class="range-text">{{settingsForm.get('bookReaderFontSize')?.value + '%'}}</span>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="row g-0">
|
||||
<div class="col-10">
|
||||
<input type="range" class="form-range" id="fontsize" min="50" max="300" step="10"
|
||||
formControlName="bookReaderFontSize">
|
||||
|
||||
<div class="section">
|
||||
<h4 id="book-reader-heading">{{t('book-reader-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('tap-to-paginate-label')" [subtitle]="t('tap-to-paginate-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch" id="tap-to-paginate"
|
||||
formControlName="bookReaderTapToPaginate" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
<span class="ps-2 col-2 align-middle">{{settingsForm.get('bookReaderFontSize')?.value + '%'}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('line-height-book-label')" [subtitle]="t('line-height-book-tooltip')">
|
||||
<ng-template #view>
|
||||
<span class="range-text">{{settingsForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="row g-0">
|
||||
<div class="col-10">
|
||||
<input type="range" class="form-range" id="linespacing" min="100" max="200" step="10"
|
||||
formControlName="bookReaderLineSpacing">
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('immersive-mode-label')" [subtitle]="t('immersive-mode-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" role="switch"
|
||||
formControlName="bookReaderImmersiveMode" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
</div>
|
||||
<span class="ps-2 col-2 align-middle">{{settingsForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('reading-direction-label')" [subtitle]="t('reading-direction-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderReadingDirection')!.value | readingDirection}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderReadingDirection">
|
||||
@for (opt of readingDirections; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | readingDirection}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('font-family-label')" [subtitle]="t('font-family-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderFontFamily')!.value | titlecase}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderFontFamily">
|
||||
@for (opt of fontFamilies; track opt) {
|
||||
<option [value]="opt">{{opt | titlecase}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('writing-style-label')" [subtitle]="t('writing-style-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderWritingStyle')!.value | writingStyle}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderWritingStyle">
|
||||
@for (opt of bookWritingStyles; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | writingStyle}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('layout-mode-book-label')" [subtitle]="t('layout-mode-book-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderLayoutMode')!.value | bookPageLayoutMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderLayoutMode">
|
||||
@for (opt of bookLayoutModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | bookPageLayoutMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('color-theme-book-label')" [subtitle]="t('color-theme-book-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('bookReaderThemeName')!.value}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderThemeName">
|
||||
@for (opt of bookColorThemesTranslated; track opt) {
|
||||
<option [value]="opt.name">{{opt.name | titlecase}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('font-size-book-label')" [subtitle]="t('font-size-book-tooltip')">
|
||||
<ng-template #view>
|
||||
<span class="range-text">{{settingsForm.get('bookReaderFontSize')?.value + '%'}}</span>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="row g-0">
|
||||
<div class="col-10">
|
||||
<input type="range" class="form-range" id="fontsize" min="50" max="300" step="10"
|
||||
formControlName="bookReaderFontSize">
|
||||
|
||||
</div>
|
||||
<span class="ps-2 col-2 align-middle">{{settingsForm.get('bookReaderFontSize')?.value + '%'}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('margin-book-label')" [subtitle]="t('margin-book-tooltip')">
|
||||
<ng-template #view>
|
||||
<span class="range-text">{{settingsForm.get('bookReaderMargin')?.value + '%'}}</span>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="row g-0">
|
||||
<div class="col-10">
|
||||
<input type="range" class="form-range" id="margin" min="0" max="30" step="5"
|
||||
formControlName="bookReaderMargin">
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('line-height-book-label')" [subtitle]="t('line-height-book-tooltip')">
|
||||
<ng-template #view>
|
||||
<span class="range-text">{{settingsForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="row g-0">
|
||||
<div class="col-10">
|
||||
<input type="range" class="form-range" id="linespacing" min="100" max="200" step="10"
|
||||
formControlName="bookReaderLineSpacing">
|
||||
</div>
|
||||
<span class="ps-2 col-2 align-middle">{{settingsForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('margin-book-label')" [subtitle]="t('margin-book-tooltip')">
|
||||
<ng-template #view>
|
||||
<span class="range-text">{{settingsForm.get('bookReaderMargin')?.value + '%'}}</span>
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<div class="row g-0">
|
||||
<div class="col-10">
|
||||
<input type="range" class="form-range" id="margin" min="0" max="30" step="5"
|
||||
formControlName="bookReaderMargin">
|
||||
</div>
|
||||
<span class="ps-2 col-2 align-middle">{{settingsForm.get('bookReaderMargin')?.value + '%'}}</span>
|
||||
</div>
|
||||
<span class="ps-2 col-2 align-middle">{{settingsForm.get('bookReaderMargin')?.value + '%'}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break"></div>
|
||||
|
||||
<h4 id="pdf-reader-heading" class="mt-3">{{t('pdf-reader-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('pdf-spread-mode-label')" [subtitle]="t('pdf-spread-mode-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('pdfSpreadMode')!.value | pdfSpreadMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="pdf-reader-heading"
|
||||
formControlName="pdfSpreadMode">
|
||||
@for (opt of pdfSpreadModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | pdfSpreadMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('pdf-theme-label')" [subtitle]="t('pdf-theme-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('pdfTheme')!.value | pdfTheme}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="pdf-reader-heading"
|
||||
formControlName="pdfTheme">
|
||||
@for (opt of pdfThemes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | pdfTheme}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('pdf-scroll-mode-label')" [subtitle]="t('pdf-scroll-mode-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('pdfScrollMode')!.value | pdfScrollMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="pdf-reader-heading"
|
||||
formControlName="pdfScrollMode">
|
||||
@for (opt of pdfScrollModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | pdfScrollMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="section">
|
||||
<h4 id="pdf-reader-heading">{{t('pdf-reader-settings-title')}}</h4>
|
||||
<ng-container>
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('pdf-spread-mode-label')" [subtitle]="t('pdf-spread-mode-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('pdfSpreadMode')!.value | pdfSpreadMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="pdf-reader-heading"
|
||||
formControlName="pdfSpreadMode">
|
||||
@for (opt of pdfSpreadModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | pdfSpreadMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('pdf-theme-label')" [subtitle]="t('pdf-theme-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('pdfTheme')!.value | pdfTheme}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="pdf-reader-heading"
|
||||
formControlName="pdfTheme">
|
||||
@for (opt of pdfThemes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | pdfTheme}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('pdf-scroll-mode-label')" [subtitle]="t('pdf-scroll-mode-tooltip')">
|
||||
<ng-template #view>
|
||||
{{settingsForm.get('pdfScrollMode')!.value | pdfScrollMode}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="pdf-reader-heading"
|
||||
formControlName="pdfScrollMode">
|
||||
@for (opt of pdfScrollModes; track opt) {
|
||||
<option [value]="opt.value">{{opt.value | pdfScrollMode}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue