Resolve some change detection issues

This commit is contained in:
shamoon 2023-12-14 00:04:11 -08:00
parent 0d7474d4af
commit a34404eac0
6 changed files with 283 additions and 286 deletions

View File

@ -94,7 +94,7 @@ The following files need to be changed:
- src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key)
- src/paperless/settings.py (in the _LANGUAGES_ array)
- src-ui/src/app/services/settings.service.ts (inside the _getLanguageOptions_ method)
- src-ui/src/app/services/settings.service.ts (inside the _LANGUAGE_OPTIONS_ array)
- src-ui/src/app/app.module.ts (import locale from _angular/common/locales_ and call _registerLocaleData_)
Please add the language in the correct order, alphabetically by locale.

View File

@ -277,27 +277,17 @@ Adding new languages requires adding the translated files in the
}
```
2. Add the language to the available options in
2. Add the language to the `LANGUAGE_OPTIONS` array in
`src-ui/src/app/services/settings.service.ts`:
```typescript
getLanguageOptions(): LanguageOption[] {
return [
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)", dateInputFormat: "mm/dd/yyyy"},
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)", dateInputFormat: "dd/mm/yyyy"},
{code: "de", name: $localize`German`, englishName: "German", dateInputFormat: "dd.mm.yyyy"},
{code: "nl", name: $localize`Dutch`, englishName: "Dutch", dateInputFormat: "dd-mm-yyyy"},
{code: "fr", name: $localize`French`, englishName: "French", dateInputFormat: "dd/mm/yyyy"},
{code: "pt-br", name: $localize`Portuguese (Brazil)`, englishName: "Portuguese (Brazil)", dateInputFormat: "dd/mm/yyyy"}
// Add your new language here
]
}
```
`dateInputFormat` is a special string that defines the behavior of
the date input fields and absolutely needs to contain "dd", "mm"
and "yyyy".
```
3. Import and register the Angular data for this locale in
`src-ui/src/app/app.module.ts`:

View File

@ -48,6 +48,12 @@ enum SettingsNavIDs {
SavedViews = 4,
}
const systemLanguage = { code: '', name: $localize`Use system language` }
const systemDateFormat = {
code: '',
name: $localize`Use date format of display language`,
}
@Component({
selector: 'pngx-settings',
templateUrl: './settings.component.html',
@ -512,15 +518,11 @@ export class SettingsComponent
}
get displayLanguageOptions(): LanguageOption[] {
return [{ code: '', name: $localize`Use system language` }].concat(
this.settings.getLanguageOptions()
)
return [systemLanguage].concat(this.settings.getLanguageOptions())
}
get dateLocaleOptions(): LanguageOption[] {
return [
{ code: '', name: $localize`Use date format of display language` },
].concat(this.settings.getDateLocaleOptions())
return [systemDateFormat].concat(this.settings.getDateLocaleOptions())
}
get today() {

View File

@ -27,6 +27,24 @@ import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
import { UploadFileWidgetComponent } from './upload-file-widget.component'
import { DragDropModule } from '@angular/cdk/drag-drop'
const FAILED_STATUSES = [new FileStatus()]
const WORKING_STATUSES = [new FileStatus(), new FileStatus()]
const STARTED_STATUSES = [new FileStatus(), new FileStatus(), new FileStatus()]
const SUCCESS_STATUSES = [
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
]
const DEFAULT_STATUSES = [
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
]
describe('UploadFileWidgetComponent', () => {
let component: UploadFileWidgetComponent
let fixture: ComponentFixture<UploadFileWidgetComponent>
@ -150,41 +168,22 @@ function mockConsumerStatuses(consumerStatusService) {
.mockImplementation((phase) => {
switch (phase) {
case FileStatusPhase.FAILED:
return [new FileStatus()]
return FAILED_STATUSES
case FileStatusPhase.WORKING:
return [new FileStatus(), new FileStatus()]
return WORKING_STATUSES
case FileStatusPhase.STARTED:
return [new FileStatus(), new FileStatus(), new FileStatus()]
return STARTED_STATUSES
case FileStatusPhase.SUCCESS:
return [
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
]
return SUCCESS_STATUSES
case FileStatusPhase.UPLOADING:
return [partialUpload1, partialUpload2]
default:
return [
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
]
return DEFAULT_STATUSES
}
})
jest
.spyOn(consumerStatusService, 'getConsumerStatusNotCompleted')
.mockImplementation(() => {
return [
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
new FileStatus(),
]
return DEFAULT_STATUSES
})
}

View File

@ -104,6 +104,51 @@ const RELATIVE_DATE_QUERYSTRINGS = [
},
]
const DEFAULT_TEXT_FILTER_TARGET_OPTIONS = [
{ id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title` },
{
id: TEXT_FILTER_TARGET_TITLE_CONTENT,
name: $localize`Title & content`,
},
{ id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN` },
{
id: TEXT_FILTER_TARGET_CUSTOM_FIELDS,
name: $localize`Custom fields`,
},
{
id: TEXT_FILTER_TARGET_FULLTEXT_QUERY,
name: $localize`Advanced search`,
},
]
const TEXT_FILTER_TARGET_MORELIKE_OPTION = {
id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE,
name: $localize`More like`,
}
const DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS = [
{
id: TEXT_FILTER_MODIFIER_EQUALS,
label: $localize`equals`,
},
{
id: TEXT_FILTER_MODIFIER_NULL,
label: $localize`is empty`,
},
{
id: TEXT_FILTER_MODIFIER_NOTNULL,
label: $localize`is not empty`,
},
{
id: TEXT_FILTER_MODIFIER_GT,
label: $localize`greater than`,
},
{
id: TEXT_FILTER_MODIFIER_LT,
label: $localize`less than`,
},
]
@Component({
selector: 'pngx-filter-editor',
templateUrl: './filter-editor.component.html',
@ -199,29 +244,12 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
_moreLikeDoc: PaperlessDocument
get textFilterTargets() {
let targets = [
{ id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title` },
{
id: TEXT_FILTER_TARGET_TITLE_CONTENT,
name: $localize`Title & content`,
},
{ id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN` },
{
id: TEXT_FILTER_TARGET_CUSTOM_FIELDS,
name: $localize`Custom fields`,
},
{
id: TEXT_FILTER_TARGET_FULLTEXT_QUERY,
name: $localize`Advanced search`,
},
]
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
targets.push({
id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE,
name: $localize`More like`,
})
return DEFAULT_TEXT_FILTER_TARGET_OPTIONS.concat([
TEXT_FILTER_TARGET_MORELIKE_OPTION,
])
}
return targets
return DEFAULT_TEXT_FILTER_TARGET_OPTIONS
}
textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
@ -234,28 +262,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
public textFilterModifier: string
get textFilterModifiers() {
return [
{
id: TEXT_FILTER_MODIFIER_EQUALS,
label: $localize`equals`,
},
{
id: TEXT_FILTER_MODIFIER_NULL,
label: $localize`is empty`,
},
{
id: TEXT_FILTER_MODIFIER_NOTNULL,
label: $localize`is not empty`,
},
{
id: TEXT_FILTER_MODIFIER_GT,
label: $localize`greater than`,
},
{
id: TEXT_FILTER_MODIFIER_LT,
label: $localize`less than`,
},
]
return DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS
}
get textFilterModifierIsNull(): boolean {

View File

@ -38,120 +38,7 @@ export interface LanguageOption {
dateInputFormat?: string
}
@Injectable({
providedIn: 'root',
})
export class SettingsService {
protected baseUrl: string = environment.apiBaseUrl + 'ui_settings/'
private settings: Object = {}
currentUser: PaperlessUser
public settingsSaved: EventEmitter<any> = new EventEmitter()
private _renderer: Renderer2
public get renderer(): Renderer2 {
return this._renderer
}
public dashboardIsEmpty: boolean = false
public globalDropzoneEnabled: boolean = true
public globalDropzoneActive: boolean = false
public organizingSidebarSavedViews: boolean = false
constructor(
rendererFactory: RendererFactory2,
@Inject(DOCUMENT) private document,
private cookieService: CookieService,
private meta: Meta,
@Inject(LOCALE_ID) private localeId: string,
protected http: HttpClient,
private toastService: ToastService,
private permissionsService: PermissionsService
) {
this._renderer = rendererFactory.createRenderer(null, null)
}
// this is called by the app initializer in app.module
public initializeSettings(): Observable<PaperlessUiSettings> {
return this.http.get<PaperlessUiSettings>(this.baseUrl).pipe(
first(),
tap((uisettings) => {
Object.assign(this.settings, uisettings.settings)
this.maybeMigrateSettings()
// to update lang cookie
if (this.settings['language']?.length)
this.setLanguage(this.settings['language'])
this.currentUser = uisettings.user
this.permissionsService.initialize(
uisettings.permissions,
this.currentUser
)
})
)
}
get displayName(): string {
return (
this.currentUser.first_name ??
this.currentUser.username ??
''
).trim()
}
public updateAppearanceSettings(
darkModeUseSystem = null,
darkModeEnabled = null,
themeColor = null
): void {
darkModeUseSystem ??= this.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM)
darkModeEnabled ??= this.get(SETTINGS_KEYS.DARK_MODE_ENABLED)
themeColor ??= this.get(SETTINGS_KEYS.THEME_COLOR)
if (darkModeUseSystem) {
this._renderer.setAttribute(
this.document.documentElement,
'data-bs-theme',
'auto'
)
} else {
this._renderer.setAttribute(
this.document.documentElement,
'data-bs-theme',
darkModeEnabled ? 'dark' : 'light'
)
}
if (themeColor) {
const hsl = hexToHsl(themeColor)
const bgBrightnessEstimate = estimateBrightnessForColor(themeColor)
if (bgBrightnessEstimate == BRIGHTNESS.DARK) {
this._renderer.addClass(this.document.body, 'primary-dark')
this._renderer.removeClass(this.document.body, 'primary-light')
} else {
this._renderer.addClass(this.document.body, 'primary-light')
this._renderer.removeClass(this.document.body, 'primary-dark')
}
document.documentElement.style.setProperty(
'--pngx-primary',
`${+hsl.h * 360},${hsl.s * 100}%`
)
document.documentElement.style.setProperty(
'--pngx-primary-lightness',
`${hsl.l * 100}%`
)
} else {
this._renderer.removeClass(this.document.body, 'primary-dark')
this._renderer.removeClass(this.document.body, 'primary-light')
document.documentElement.style.removeProperty('--pngx-primary')
document.documentElement.style.removeProperty('--pngx-primary-lightness')
}
}
getLanguageOptions(): LanguageOption[] {
const languages = [
const LANGUAGE_OPTIONS = [
{
code: 'en-us',
name: $localize`English (US)`,
@ -340,21 +227,133 @@ export class SettingsService {
},
]
// Sort languages by localized name at runtime
languages.sort((a, b) => {
return a.name < b.name ? -1 : 1
})
return languages
}
getDateLocaleOptions(): LanguageOption[] {
let isoOption: LanguageOption = {
const ISO_LANGUAGE_OPTION: LanguageOption = {
code: 'iso-8601',
name: $localize`ISO 8601`,
dateInputFormat: 'yyyy-mm-dd',
}
return [isoOption].concat(this.getLanguageOptions())
@Injectable({
providedIn: 'root',
})
export class SettingsService {
protected baseUrl: string = environment.apiBaseUrl + 'ui_settings/'
private settings: Object = {}
currentUser: PaperlessUser
public settingsSaved: EventEmitter<any> = new EventEmitter()
private _renderer: Renderer2
public get renderer(): Renderer2 {
return this._renderer
}
public dashboardIsEmpty: boolean = false
public globalDropzoneEnabled: boolean = true
public globalDropzoneActive: boolean = false
public organizingSidebarSavedViews: boolean = false
constructor(
rendererFactory: RendererFactory2,
@Inject(DOCUMENT) private document,
private cookieService: CookieService,
private meta: Meta,
@Inject(LOCALE_ID) private localeId: string,
protected http: HttpClient,
private toastService: ToastService,
private permissionsService: PermissionsService
) {
this._renderer = rendererFactory.createRenderer(null, null)
}
// this is called by the app initializer in app.module
public initializeSettings(): Observable<PaperlessUiSettings> {
return this.http.get<PaperlessUiSettings>(this.baseUrl).pipe(
first(),
tap((uisettings) => {
Object.assign(this.settings, uisettings.settings)
this.maybeMigrateSettings()
// to update lang cookie
if (this.settings['language']?.length)
this.setLanguage(this.settings['language'])
this.currentUser = uisettings.user
this.permissionsService.initialize(
uisettings.permissions,
this.currentUser
)
})
)
}
get displayName(): string {
return (
this.currentUser.first_name ??
this.currentUser.username ??
''
).trim()
}
public updateAppearanceSettings(
darkModeUseSystem = null,
darkModeEnabled = null,
themeColor = null
): void {
darkModeUseSystem ??= this.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM)
darkModeEnabled ??= this.get(SETTINGS_KEYS.DARK_MODE_ENABLED)
themeColor ??= this.get(SETTINGS_KEYS.THEME_COLOR)
if (darkModeUseSystem) {
this._renderer.setAttribute(
this.document.documentElement,
'data-bs-theme',
'auto'
)
} else {
this._renderer.setAttribute(
this.document.documentElement,
'data-bs-theme',
darkModeEnabled ? 'dark' : 'light'
)
}
if (themeColor) {
const hsl = hexToHsl(themeColor)
const bgBrightnessEstimate = estimateBrightnessForColor(themeColor)
if (bgBrightnessEstimate == BRIGHTNESS.DARK) {
this._renderer.addClass(this.document.body, 'primary-dark')
this._renderer.removeClass(this.document.body, 'primary-light')
} else {
this._renderer.addClass(this.document.body, 'primary-light')
this._renderer.removeClass(this.document.body, 'primary-dark')
}
document.documentElement.style.setProperty(
'--pngx-primary',
`${+hsl.h * 360},${hsl.s * 100}%`
)
document.documentElement.style.setProperty(
'--pngx-primary-lightness',
`${hsl.l * 100}%`
)
} else {
this._renderer.removeClass(this.document.body, 'primary-dark')
this._renderer.removeClass(this.document.body, 'primary-light')
document.documentElement.style.removeProperty('--pngx-primary')
document.documentElement.style.removeProperty('--pngx-primary-lightness')
}
}
getLanguageOptions(): LanguageOption[] {
// Sort languages by localized name at runtime
return LANGUAGE_OPTIONS.sort((a, b) => {
return a.name < b.name ? -1 : 1
})
}
getDateLocaleOptions(): LanguageOption[] {
return [ISO_LANGUAGE_OPTION].concat(this.getLanguageOptions())
}
private getLanguageCookieName() {