Resolve some change detection issues
This commit is contained in:
parent
0d7474d4af
commit
a34404eac0
@ -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.
|
||||
|
@ -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`:
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user