[#74950] Migrate Angular DI to inject()

Migrates Angular constructor-based dependency injection to the
`inject()` function. The initial pass used the Angular schematic;
manual follow-up handled abstract classes, inheritance-sensitive
constructors, and call sites that still instantiate services
directly.

Schematic command:

  cd frontend && npx ng generate @angular/core:inject-migration \
    --path ./ \
    --migrate-abstract-classes \
    --backwards-compatible-constructors=false \
    --non-nullable-optional=false

https://community.openproject.org/wp/74950
This commit is contained in:
Alexander Brandon Coles
2026-05-13 21:03:34 +02:00
parent 90acd7a022
commit c32f2f2e26
440 changed files with 2827 additions and 4920 deletions
@@ -1,4 +1,4 @@
import { Inject, Injectable, DOCUMENT } from '@angular/core';
import { Injectable, DOCUMENT, inject } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { debugLog } from 'core-app/shared/helpers/debug_output';
@@ -6,7 +6,9 @@ import { debugLog } from 'core-app/shared/helpers/debug_output';
export class ActiveWindowService {
private activeState$ = new BehaviorSubject<boolean>(true);
constructor(@Inject(DOCUMENT) document:Document) {
constructor() {
const document = inject<Document>(DOCUMENT);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState) {
debugLog(`Browser window has visibility state changed to ${document.visibilityState}`);
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { ApiV3GettableResource, ApiV3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { Constructor } from 'core-app/core/util-types';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
@@ -65,6 +65,9 @@ import {
@Injectable({ providedIn: 'root' })
export class ApiV3Service {
readonly injector = inject(Injector);
readonly pathHelper = inject(PathHelperService);
// /api/v3/attachments
public readonly attachments = this.apiV3CollectionEndpoint('attachments');
@@ -173,11 +176,6 @@ export class ApiV3Service {
// VIRTUAL boards are /api/v3/grids + a scope filter
public readonly boards = this.apiV3CustomEndpoint(ApiV3BoardsPaths);
constructor(
readonly injector:Injector,
readonly pathHelper:PathHelperService,
) { }
/**
* Returns the part of the API that exists both
* - WITHIN a project scope /api/v3/projects/*
@@ -37,8 +37,8 @@ import { ProjectResource } from 'core-app/features/hal/resources/project-resourc
export class ProjectCache extends StateCacheService<ProjectResource> {
@InjectField() private schemaCacheService:SchemaCacheService;
constructor(readonly injector:Injector,
state:MultiInputState<ProjectResource>) {
// eslint-disable-next-line @angular-eslint/prefer-inject -- manually instantiated, not DI-resolved
constructor(readonly injector:Injector, state:MultiInputState<ProjectResource>) {
super(state);
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { NgModule } from '@angular/core';
import { NgModule, inject } from '@angular/core';
import { OpenprojectModalModule } from 'core-app/shared/components/modal/modal.module';
import { OpModalWrapperAugmentService } from 'core-app/shared/components/modal/modal-wrapper-augment.service';
@@ -34,7 +34,9 @@ import { OpModalWrapperAugmentService } from 'core-app/shared/components/modal/m
imports: [OpenprojectModalModule],
})
export class OpenprojectAugmentingModule {
constructor(modalWrapper:OpModalWrapperAugmentService) {
constructor() {
const modalWrapper = inject(OpModalWrapperAugmentService);
// Setup augmenting services
modalWrapper.setupListener();
@@ -26,17 +26,15 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { Observable } from 'rxjs';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
@Injectable({ providedIn: 'root' })
export class OpenProjectBackupService {
constructor(
protected apiV3Service:ApiV3Service,
) {
}
protected apiV3Service = inject(ApiV3Service);
public triggerBackup(backupToken:string, includeAttachments = true):Observable<HalResource> {
return this
@@ -1,9 +1,9 @@
import { Inject, Injectable, DOCUMENT } from '@angular/core';
import { Injectable, DOCUMENT, inject } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class BrowserDetector {
constructor(@Inject(DOCUMENT) private documentElement:Document) {
}
private documentElement = inject<Document>(DOCUMENT);
/**
* Detect mobile browser based on the Rails determined UA
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import moment from 'moment';
import { ConfigurationResource } from 'core-app/features/hal/resources/configuration-resource';
@@ -35,15 +35,13 @@ import { type DurationFormat } from 'core-app/shared/helpers/chronic_duration';
@Injectable({ providedIn: 'root' })
export class ConfigurationService {
private readonly apiV3Service = inject(ApiV3Service);
// fetches configuration from the ApiV3 endpoint
// TODO: this currently saves the request between page reloads,
// but could easily be stored in localStorage
private configuration:ConfigurationResource;
public constructor(
private readonly apiV3Service:ApiV3Service,
) { }
public initialize():Promise<void> {
return this.loadConfiguration();
}
@@ -26,7 +26,9 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { TestBed } from '@angular/core/testing';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { CurrentProjectService } from './current-project.service';
describe('currentProject service', () => {
@@ -40,7 +42,15 @@ describe('currentProject service', () => {
};
beforeEach(() => {
currentProject = new CurrentProjectService(new PathHelperService(), apiV3Stub);
TestBed.configureTestingModule({
providers: [
CurrentProjectService,
PathHelperService,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
{ provide: ApiV3Service, useValue: apiV3Stub },
],
});
currentProject = TestBed.inject(CurrentProjectService);
});
describe('with no meta present', () => {
@@ -26,21 +26,21 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { getMetaElement } from '../setup/globals/global-helpers';
@Injectable({ providedIn: 'root' })
export class CurrentProjectService {
private PathHelper = inject(PathHelperService);
private apiV3Service = inject(ApiV3Service);
private currentId:string|null = null;
private currentName:string|null = null;
private currentIdentifier:string|null = null;
constructor(
private PathHelper:PathHelperService,
private apiV3Service:ApiV3Service,
) {
constructor() {
this.detect();
}
@@ -1,4 +1,4 @@
import { Injector, NgModule } from '@angular/core';
import { Injector, NgModule, inject } from '@angular/core';
import { CurrentUserService } from './current-user.service';
import { CurrentUserStore } from './current-user.store';
@@ -35,7 +35,9 @@ export function bootstrapModule(injector:Injector):void {
],
})
export class CurrentUserModule {
constructor(injector:Injector) {
constructor() {
const injector = inject(Injector);
bootstrapModule(injector);
}
}
@@ -1,11 +1,17 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Query } from '@datorama/akita';
import { CurrentUserState, CurrentUserStore } from './current-user.store';
@Injectable()
export class CurrentUserQuery extends Query<CurrentUserState> {
constructor(protected store:CurrentUserStore) {
protected store:CurrentUserStore;
constructor() {
const store = inject(CurrentUserStore);
super(store);
this.store = store;
}
isLoggedIn$ = this.select((state) => !!state.loggedIn);
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ApiV3ListFilter } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { CapabilitiesResourceService } from 'core-app/core/state/capabilities/capabilities.service';
@@ -38,12 +38,12 @@ import { CurrentUser, CurrentUserStore } from './current-user.store';
@Injectable({ providedIn: 'root' })
export class CurrentUserService {
constructor(
private apiV3Service:ApiV3Service,
private currentUserStore:CurrentUserStore,
private currentUserQuery:CurrentUserQuery,
private capabilitiesService:CapabilitiesResourceService,
) {
private apiV3Service = inject(ApiV3Service);
private currentUserStore = inject(CurrentUserStore);
private currentUserQuery = inject(CurrentUserQuery);
private capabilitiesService = inject(CapabilitiesResourceService);
constructor() {
this.setupLegacyDataListeners();
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import moment, { Moment } from 'moment-timezone';
@@ -34,10 +34,9 @@ import { outputChronicDuration } from '../../shared/helpers/chronic_duration';
@Injectable({ providedIn: 'root' })
export class TimezoneService {
constructor(
readonly configurationService:ConfigurationService,
readonly I18n:I18nService,
) { }
readonly configurationService = inject(ConfigurationService);
readonly I18n = inject(I18nService);
/**
* Returns the user's configured timezone or guesses it through moment
@@ -26,10 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
Injectable,
Injector,
} from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import moment, { Moment } from 'moment';
import {
take,
@@ -45,14 +42,12 @@ import {
@Injectable({ providedIn: 'root' })
export class WeekdayService {
readonly injector = inject(Injector);
@InjectField() weekdaysService:WeekdayResourceService;
private weekdays:IWeekday[];
constructor(
readonly injector:Injector,
) {}
/**
* @param date The iso day number (1-7) or a date instance
* @return {boolean} whether the given iso day is working or not
@@ -26,18 +26,20 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Inject, Injectable, DOCUMENT } from '@angular/core';
import { Injectable, DOCUMENT, inject } from '@angular/core';
import { enterpriseEditionUrl } from 'core-app/core/setup/globals/constants.const';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
@Injectable({ providedIn: 'root' })
export class BannersService {
protected documentElement = inject<Document>(DOCUMENT);
protected configuration = inject(ConfigurationService);
private readonly _bannersHidden:boolean = true;
constructor(
@Inject(DOCUMENT) protected documentElement:Document,
protected configuration:ConfigurationService,
) {
constructor() {
const documentElement = this.documentElement;
this._bannersHidden = documentElement.body.classList.contains('ee-banners-hidden');
}
@@ -1,16 +1,5 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
HostListener,
Input,
OnDestroy,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, Input, OnDestroy, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { first, map, switchMap, tap } from 'rxjs/operators';
import { GlobalSearchService } from 'core-app/core/global_search/services/global-search.service';
@@ -74,6 +63,18 @@ interface SearchResultItems {
standalone: false,
})
export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
readonly elementRef = inject(ElementRef);
readonly I18n = inject(I18nService);
readonly apiV3Service = inject(ApiV3Service);
readonly pathHelperService = inject(PathHelperService);
readonly halResourceService = inject(HalResourceService);
readonly globalSearchService = inject(GlobalSearchService);
readonly currentProjectService = inject(CurrentProjectService);
readonly deviceService = inject(DeviceService);
readonly cdRef = inject(ChangeDetectorRef);
readonly halNotification = inject(HalResourceNotificationService);
readonly recentItemsService = inject(RecentItemsService);
@Input() public placeholder:string;
@ViewChild('btn', { static: true }) btn:ElementRef;
@@ -131,19 +132,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
search: this.I18n.t('js.autocompleter.search'),
};
constructor(
readonly elementRef:ElementRef,
readonly I18n:I18nService,
readonly apiV3Service:ApiV3Service,
readonly pathHelperService:PathHelperService,
readonly halResourceService:HalResourceService,
readonly globalSearchService:GlobalSearchService,
readonly currentProjectService:CurrentProjectService,
readonly deviceService:DeviceService,
readonly cdRef:ChangeDetectorRef,
readonly halNotification:HalResourceNotificationService,
readonly recentItemsService:RecentItemsService,
) {
constructor() {
populateInputsFromDataset(this);
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injector, NgModule } from '@angular/core';
import { Injector, NgModule, inject } from '@angular/core';
import { OpenprojectWorkPackagesModule } from 'core-app/features/work-packages/openproject-work-packages.module';
import { GlobalSearchInputComponent } from 'core-app/core/global_search/input/global-search-input.component';
import { GlobalSearchWorkPackagesComponent } from 'core-app/core/global_search/global-search-work-packages.component';
@@ -53,6 +53,5 @@ import { RecentItemsService } from 'core-app/core/recent-items.service';
],
})
export class OpenprojectGlobalSearchModule {
constructor(readonly injector:Injector) {
}
readonly injector = inject(Injector);
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
@@ -34,13 +34,11 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service
@Injectable()
export class GlobalSearchService {
constructor(
protected I18n:I18nService,
protected injector:Injector,
protected PathHelper:PathHelperService,
protected currentProjectService:CurrentProjectService,
) {
}
protected I18n = inject(I18nService);
protected injector = inject(Injector);
protected PathHelper = inject(PathHelperService);
protected currentProjectService = inject(CurrentProjectService);
public submitSearch(query:string, scope:string):void {
const path = this.searchPath(scope);
@@ -26,8 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit } from '@angular/core';
import { StateService } from '@uirouter/core';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core';
import { Subscription } from 'rxjs';
import { GlobalSearchService } from 'core-app/core/global_search/services/global-search.service';
import { ScrollableTabsComponent } from 'core-app/shared/components/tabs/scrollable-tabs/scrollable-tabs.component';
@@ -40,20 +39,13 @@ import { TabDefinition } from 'core-app/shared/components/tabs/tab.interface';
standalone: false,
})
export class GlobalSearchTabsComponent extends ScrollableTabsComponent implements OnInit, OnDestroy {
readonly globalSearchService = inject(GlobalSearchService);
private currentTabSub:Subscription;
private tabsSub:Subscription;
public classes:string[] = ['global-search--tabs', 'scrollable-tabs'];
constructor(
readonly globalSearchService:GlobalSearchService,
protected readonly $state:StateService,
public injector:Injector,
cdRef:ChangeDetectorRef,
) {
super($state, cdRef, injector);
}
public override classes:string[] = ['global-search--tabs', 'scrollable-tabs'];
ngOnInit():void {
this.currentTabSub = this.globalSearchService
@@ -26,12 +26,13 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, SecurityContext } from '@angular/core';
import { Injectable, SecurityContext, inject } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Injectable({ providedIn: 'root' })
export class HTMLSanitizeService {
public constructor(readonly sanitizer:DomSanitizer) { }
readonly sanitizer = inject(DomSanitizer);
public sanitize(string:string):SafeHtml {
return this.sanitizer.sanitize(SecurityContext.HTML, string) || '';
@@ -1,13 +1,13 @@
import { Title } from '@angular/platform-browser';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { getMetaContent } from '../setup/globals/global-helpers';
const titlePartsSeparator = ' | ';
@Injectable({ providedIn: 'root' })
export class OpTitleService {
constructor(private titleService:Title) {
}
private titleService = inject(Title);
public get current():string {
return this.titleService.getTitle();
+4 -4
View File
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { NgSelectConfig } from '@ng-select/ng-select';
import { I18n } from 'i18n-js';
import { FormatNumberOptions, TranslateOptions } from 'i18n-js/src/typing';
@@ -6,12 +6,12 @@ import { getMetaValue } from '../setup/globals/global-helpers';
@Injectable({ providedIn: 'root' })
export class I18nService {
private config = inject(NgSelectConfig);
private i18n:I18n = window.I18n;
private instanceLocale:string;
constructor(
private config:NgSelectConfig,
) {
constructor() {
this.instanceLocale = getMetaValue('openproject_initializer', 'instanceLocale', 'en');
this.config.addTagText = this.t('js.autocomplete_ng_select.add_tag');
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { DeviceService } from 'core-app/core/browser/device.service';
@@ -35,6 +35,9 @@ import { queryVisible } from 'core-app/shared/helpers/dom-helpers';
@Injectable({ providedIn: 'root' })
export class MainMenuToggleService {
injector = inject(Injector);
readonly deviceService = inject(DeviceService);
private elementWidth:number;
private elementMinWidth = 11;
@@ -61,10 +64,7 @@ export class MainMenuToggleService {
private wasCollapsedByUser = false;
constructor(
public injector:Injector,
readonly deviceService:DeviceService,
) {
constructor() {
this.initializeMenu();
// Add resize event listener
window.addEventListener('resize', this.onWindowResize.bind(this));
@@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { type FrameElement } from '@hotwired/turbo';
import { StateService } from '@uirouter/core';
@Injectable({ providedIn: 'root' })
export class SubmenuService {
constructor(protected $state:StateService) {}
protected $state = inject(StateService);
reloadSubmenu(selectedQueryId:string|null, sidemenuId?:string):void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
@@ -26,15 +26,15 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { map, shareReplay, startWith } from 'rxjs/operators';
import { NavigationService } from 'core-app/core/navigation/navigation.service';
@Injectable({ providedIn: 'root' })
export class UrlParamsService {
constructor(private navigation:NavigationService) {
}
private navigation = inject(NavigationService);
public get(key:string):string|null {
return this.searchParams.get(key);
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injector, NgModule } from '@angular/core';
import { Injector, NgModule, inject } from '@angular/core';
import { FirstRouteService } from 'core-app/core/routing/first-route-service';
import { UIRouterModule } from '@uirouter/angular';
import { ApplicationBaseComponent } from 'core-app/core/routing/base/application-base.component';
@@ -52,7 +52,9 @@ import {
],
})
export class OpenprojectRouterModule {
constructor(injector:Injector) {
constructor() {
const injector = inject(Injector);
initializeUiRouterListeners(injector);
}
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { State } from '@openproject/reactivestates';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { firstValueFrom, Observable } from 'rxjs';
import { take } from 'rxjs/operators';
@@ -41,13 +41,17 @@ export const fallbackSchemaId = '__fallback';
@Injectable()
export class SchemaCacheService extends StateCacheService<SchemaResource> {
readonly states:States;
readonly halResourceService = inject(HalResourceService);
fallbackSchema = this.halResourceService.createHalResourceOfClass<SchemaResource>(SchemaResource, {}, true);
constructor(
readonly states:States,
readonly halResourceService:HalResourceService,
) {
constructor() {
const states = inject(States);
super(states.schemas);
this.states = states;
this.putValue(fallbackSchemaId, this.fallbackSchema);
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Injector, ViewChild } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Injector, ViewChild, inject } from '@angular/core';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
@@ -43,6 +43,13 @@ import { JobStatusModalService } from 'core-app/features/job-status/job-status-m
standalone: false,
})
export class BackupComponent implements AfterViewInit {
readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
injector = inject(Injector);
protected i18n = inject(I18nService);
protected toastService = inject(ToastService);
protected pathHelper = inject(PathHelperService);
protected jobStatusModalService = inject(JobStatusModalService);
public text = {
info: this.i18n.t('js.backup.info'),
note: this.i18n.t('js.backup.note'),
@@ -72,14 +79,7 @@ export class BackupComponent implements AfterViewInit {
@ViewChild('backupTokenInput') backupTokenInput:ElementRef<HTMLInputElement>;
constructor(
readonly elementRef:ElementRef<HTMLElement>,
public injector:Injector,
protected i18n:I18nService,
protected toastService:ToastService,
protected pathHelper:PathHelperService,
protected jobStatusModalService:JobStatusModalService,
) {
constructor() {
this.includeAttachments = this.mayIncludeAttachments;
}
@@ -58,10 +58,7 @@ import {
HttpClient,
HttpErrorResponse,
} from '@angular/common/http';
import {
Injectable,
Injector,
} from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import idFromLink from 'core-app/features/hal/helpers/id-from-link';
@@ -76,18 +73,15 @@ export type ResourceKeyInput = ApiV3ListParameters|string;
@Injectable()
export abstract class ResourceStoreService<T extends { id:ID }> {
readonly injector = inject(Injector);
readonly http = inject(HttpClient);
readonly apiV3Service = inject(ApiV3Service);
readonly toastService = inject(ToastService);
protected store:ResourceStore<T> = this.createStore();
protected query = new QueryEntity(this.store);
constructor(
readonly injector:Injector,
readonly http:HttpClient,
readonly apiV3Service:ApiV3Service,
readonly toastService:ToastService,
) {
}
/**
* Require the results for the given filter params
* Returns a cached set if it was loaded already.
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { combineLatest, Observable } from 'rxjs';
import {
filter, map, take, tap,
@@ -44,15 +44,13 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
@Injectable()
export class StorageFilesResourceService {
private readonly httpClient = inject(HttpClient);
private readonly apiV3Service = inject(ApiV3Service);
private readonly store:StorageFilesStore = new StorageFilesStore();
private readonly query = new QueryEntity(this.store);
constructor(
private readonly httpClient:HttpClient,
private readonly apiV3Service:ApiV3Service,
) {}
files(link:IHalResourceLink):Observable<IStorageFiles> {
const value = this.store.getValue().files[link.href];
if (value !== undefined) {
@@ -26,10 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
Inject,
Injectable,
} from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { isVisible } from 'core-app/shared/helpers/dom-helpers';
@@ -37,8 +34,8 @@ export const ANIMATION_RATE_MS = 100;
@Injectable({ providedIn: 'root' })
export class TopMenuService {
constructor(@Inject(DOCUMENT) private document:Document) {
}
private document = inject<Document>(DOCUMENT);
register():void {
this.skipContentClickListener();
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { renderStreamMessage } from '@hotwired/turbo';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { debugLog } from 'core-app/shared/helpers/debug_output';
@@ -7,14 +7,10 @@ import { getMetaContent } from '../setup/globals/global-helpers';
@Injectable({ providedIn: 'root' })
export class TurboRequestsService {
private toast = inject(ToastService);
#controllers = new Map<string, AbortController>();
constructor(
private toast:ToastService,
) {
}
public request(
url:string,
init:RequestInit = {},
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import {
ExternalQueryConfigurationService,
@@ -11,6 +11,11 @@ import {
standalone: false,
})
export class EditableQueryPropsComponent implements OnInit {
private elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
private I18n = inject(I18nService);
private cdRef = inject(ChangeDetectorRef);
private externalQuery = inject(ExternalQueryConfigurationService);
id:string|null;
name:string|null;
@@ -23,14 +28,6 @@ export class EditableQueryPropsComponent implements OnInit {
edit_query: this.I18n.t('js.admin.type_form.edit_query'),
};
constructor(
private elementRef:ElementRef<HTMLElement>,
private I18n:I18nService,
private cdRef:ChangeDetectorRef,
private externalQuery:ExternalQueryConfigurationService,
) {
}
ngOnInit() {
const element = this.elementRef.nativeElement;
this.id = element.dataset.id!;
@@ -26,12 +26,14 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { BcfResourceCollectionPath } from 'core-app/features/bim/bcf/api/bcf-path-resources';
import { BcfProjectPaths } from 'core-app/features/bim/bcf/api/projects/bcf-project.paths';
@Injectable({ providedIn: 'root' })
export class BcfApiService {
readonly injector = inject(Injector);
public readonly bcfApiVersion = '2.1';
public readonly appBasePath = window.appBasePath || '';
@@ -41,9 +43,6 @@ export class BcfApiService {
// /api/bcf/:version/projects
public readonly projects = new BcfResourceCollectionPath(this.injector, this.bcfApiBase, 'projects', BcfProjectPaths);
constructor(readonly injector:Injector) {
}
/**
* Parse the given string into a BCF resource path
*
@@ -6,18 +6,17 @@ import {
Observable,
} from 'rxjs';
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
export type AllowedExtensionKey = keyof BcfExtensionResource;
@Injectable({ providedIn: 'root' })
export class BcfAuthorizationService {
readonly bcfApi = inject(BcfApiService);
// Poor mans caching to avoid repeatedly fetching from the backend.
protected authorizationMap = multiInput<BcfExtensionResource>();
constructor(readonly bcfApi:BcfApiService) {
}
/**
* Returns an observable boolean whether the given action
* is authorized in the project by using the project extensions.
@@ -1,4 +1,4 @@
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
@@ -7,6 +7,8 @@ import { CreateBcfViewpointData } from 'core-app/features/bim/bcf/api/bcf-api.mo
@Injectable()
export abstract class ViewerBridgeService {
readonly injector = inject(Injector);
@InjectField() state:StateService;
/**
@@ -14,8 +16,6 @@ export abstract class ViewerBridgeService {
*/
abstract shouldShowViewer:boolean;
protected constructor(readonly injector:Injector) {}
/**
* Get a viewpoint from the viewer
*/
@@ -26,17 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
Optional,
ViewChild,
} from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { StateService } from '@uirouter/core';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { NgxGalleryComponent, NgxGalleryOptions } from '@kolkov/ngx-gallery';
@@ -62,6 +52,17 @@ import { filter, take } from 'rxjs/operators';
standalone: false,
})
export class BcfWpAttributeGroupComponent extends UntilDestroyedMixin implements AfterViewInit, OnDestroy, OnInit {
readonly state = inject(StateService);
readonly bcfAuthorization = inject(BcfAuthorizationService);
readonly viewerBridge = inject(ViewerBridgeService);
readonly apiV3Service = inject(ApiV3Service);
readonly wpCreate = inject(WorkPackageCreateService);
readonly toastService = inject(ToastService);
readonly bcfViewer = inject(BcfViewService, { optional: true });
readonly cdRef = inject(ChangeDetectorRef);
readonly I18n = inject(I18nService);
readonly viewpointsService = inject(ViewpointsService);
@Input() workPackage:WorkPackageResource;
@ViewChild(NgxGalleryComponent) gallery:NgxGalleryComponent;
@@ -146,19 +147,6 @@ export class BcfWpAttributeGroupComponent extends UntilDestroyedMixin implements
projectId:string;
constructor(readonly state:StateService,
readonly bcfAuthorization:BcfAuthorizationService,
readonly viewerBridge:ViewerBridgeService,
readonly apiV3Service:ApiV3Service,
readonly wpCreate:WorkPackageCreateService,
readonly toastService:ToastService,
@Optional() readonly bcfViewer:BcfViewService,
readonly cdRef:ChangeDetectorRef,
readonly I18n:I18nService,
readonly viewpointsService:ViewpointsService) {
super();
}
ngAfterViewInit():void {
// Observe changes on the work package to update the viewpoints
this.observeChanges();
@@ -1,9 +1,9 @@
import { Inject, Injectable, DOCUMENT } from '@angular/core';
import { Injectable, DOCUMENT, inject } from '@angular/core';
@Injectable()
export class BcfDetectorService {
constructor(@Inject(DOCUMENT) private documentElement:Document) {
}
private documentElement = inject<Document>(DOCUMENT);
/**
* Detect whether the BCF module was activated,
@@ -26,14 +26,14 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { HalLink } from 'core-app/features/hal/hal-link/hal-link';
@Injectable()
export class BcfPathHelperService {
constructor(readonly pathHelper:PathHelperService) {
}
readonly pathHelper = inject(PathHelperService);
public projectImportIssuePath(projectIdentifier:string) {
return `${this.pathHelper.projectPath(projectIdentifier)}/issues/upload`;
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { BcfApiService } from 'core-app/features/bim/bcf/api/bcf-api.service';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
@@ -42,6 +42,8 @@ import { HalResource } from 'core-app/features/hal/resources/hal-resource';
@Injectable()
export class ViewpointsService {
readonly injector = inject(Injector);
topicUUID:string|number|null = null;
@InjectField() bcfApi:BcfApiService;
@@ -50,8 +52,6 @@ export class ViewpointsService {
@InjectField() apiV3Service:ApiV3Service;
constructor(readonly injector:Injector) { }
public getViewPointResource(workPackage:WorkPackageResource, index:number):BcfViewpointPaths {
const viewpointHref = (workPackage.bcfViewpoints as HalResource[])[index].href!;
@@ -0,0 +1,40 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injector } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { IFCViewerService } from 'core-app/features/bim/ifc_models/ifc-viewer/ifc-viewer.service';
import { viewerBridgeServiceFactory } from 'core-app/features/bim/bcf/openproject-bcf.module';
describe('viewerBridgeServiceFactory', () => {
it('falls back to an IFC viewer service when none is registered', () => {
const injector = TestBed.inject(Injector);
expect(viewerBridgeServiceFactory(injector)).toBeInstanceOf(IFCViewerService);
});
});
@@ -29,6 +29,8 @@
import {
Injector,
NgModule,
inject,
runInInjectionContext,
} from '@angular/core';
import { OpSharedModule } from 'core-app/shared/shared.module';
import { NgxGalleryModule } from '@kolkov/ngx-gallery';
@@ -57,9 +59,12 @@ import { RefreshButtonComponent } from 'core-app/features/bim/ifc_models/toolbar
*/
export const viewerBridgeServiceFactory = (injector:Injector) => {
if (window.navigator.userAgent.search('Revit') > -1) {
return new RevitBridgeService(injector);
return runInInjectionContext(injector, () => new RevitBridgeService());
}
return injector.get(IFCViewerService, new IFCViewerService(injector));
const ifcViewerService = injector.get(IFCViewerService, null);
return ifcViewerService ?? runInInjectionContext(injector, () => new IFCViewerService());
};
@NgModule({
@@ -93,7 +98,9 @@ export const viewerBridgeServiceFactory = (injector:Injector) => {
export class OpenprojectBcfModule {
static bootstrapCalled = false;
constructor(injector:Injector) {
constructor() {
const injector = inject(Injector);
OpenprojectBcfModule.bootstrap(injector);
}
@@ -26,11 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy,
Component,
OnInit,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bcf-view.service';
@@ -42,9 +38,9 @@ import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bc
standalone: false,
})
export class BcfSplitLeftComponent implements OnInit {
showViewer$:Observable<boolean>;
private readonly bcfView = inject(BcfViewService);
constructor(private readonly bcfView:BcfViewService) {}
showViewer$:Observable<boolean>;
ngOnInit():void {
this.showViewer$ = this.bcfView.live$()
@@ -26,11 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy,
Component,
OnInit,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bcf-view.service';
import { map } from 'rxjs/operators';
@@ -42,9 +38,9 @@ import { map } from 'rxjs/operators';
standalone: false,
})
export class BcfSplitRightComponent implements OnInit {
showWorkPackages$:Observable<boolean>;
private readonly bcfView = inject(BcfViewService);
constructor(private readonly bcfView:BcfViewService) {}
showWorkPackages$:Observable<boolean>;
ngOnInit():void {
this.showWorkPackages$ = this.bcfView.live$()
@@ -26,16 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
HostListener,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { IFCViewerService } from 'core-app/features/bim/ifc_models/ifc-viewer/ifc-viewer.service';
import { IfcModelsDataService } from 'core-app/features/bim/ifc_models/pages/viewer/ifc-models-data.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
@@ -56,6 +47,12 @@ import { filter, take } from 'rxjs/operators';
standalone: false,
})
export class IFCViewerComponent implements OnInit, OnDestroy, AfterViewInit {
ifcData = inject(IfcModelsDataService);
private I18n = inject(I18nService);
private ifcViewerService = inject(IFCViewerService);
private currentUserService = inject(CurrentUserService);
private currentProjectService = inject(CurrentProjectService);
private viewInitialized$ = new Subject<void>();
modelCount:number = this.ifcData.models.length;
@@ -86,14 +83,6 @@ export class IFCViewerComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild('xeokitToolbarIcons') xeokitToolbarIcons:ElementRef;
constructor(
public ifcData:IfcModelsDataService,
private I18n:I18nService,
private ifcViewerService:IFCViewerService,
private currentUserService:CurrentUserService,
private currentProjectService:CurrentProjectService,
) { }
ngOnInit():void {
if (this.modelCount === 0) {
return;
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable } from '@angular/core';
import { XeokitServer } from 'core-app/features/bim/ifc_models/xeokit/xeokit-server';
import { ViewerBridgeService } from 'core-app/features/bim/bcf/bcf-viewer-bridge/viewer-bridge.service';
import { BehaviorSubject, Observable, of } from 'rxjs';
@@ -123,10 +123,6 @@ export class IFCViewerService extends ViewerBridgeService {
@InjectField() httpClient:HttpClient;
constructor(readonly injector:Injector) {
super(injector);
}
public newViewer(elements:XeokitElements, projects:IfcProjectDefinition[]):void {
const server = new XeokitServer(this.pathHelper, this.ifcModelsDataService);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
@@ -26,10 +26,9 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { WorkPackageQueryStateService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-base.service';
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
import { ViewerBridgeService } from 'core-app/features/bim/bcf/bcf-viewer-bridge/viewer-bridge.service';
@@ -43,6 +42,9 @@ export type BcfViewState = 'cards'|'viewer'|'splitTable'|'splitCards'|'table';
@Injectable()
export class BcfViewService extends WorkPackageQueryStateService<BcfViewState> {
private readonly I18n = inject(I18nService);
private readonly viewerBridgeService = inject(ViewerBridgeService);
public text:Record<string, string> = {
cards: this.I18n.t('js.views.card'),
viewer: this.I18n.t('js.ifc_models.views.viewer'),
@@ -59,14 +61,6 @@ export class BcfViewService extends WorkPackageQueryStateService<BcfViewState> {
table: 'icon-view-list',
};
constructor(
private readonly I18n:I18nService,
private readonly viewerBridgeService:ViewerBridgeService,
protected readonly querySpace:IsolatedQuerySpace,
) {
super(querySpace);
}
hasChanged(query:QueryResource):boolean {
return this.current !== query.displayRepresentation;
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, Injector, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit, OnDestroy, ViewEncapsulation, inject } from '@angular/core';
import {
PartitionedQuerySpacePageComponent,
@@ -94,6 +94,10 @@ import {
export class IFCViewerPageComponent
extends PartitionedQuerySpacePageComponent
implements UntilDestroyedMixin, OnInit, OnDestroy {
readonly ifcData = inject(IfcModelsDataService);
readonly bcfView = inject(BcfViewService);
readonly viewerBridgeService = inject(ViewerBridgeService);
text = {
title: this.I18n.t('js.bcf.management'),
delete: this.I18n.t('js.button_delete'),
@@ -157,15 +161,6 @@ export class IFCViewerPageComponent
// eslint-disable-next-line @typescript-eslint/ban-types
private removeSubscription:Function;
constructor(
readonly ifcData:IfcModelsDataService,
readonly bcfView:BcfViewService,
readonly injector:Injector,
readonly viewerBridgeService:ViewerBridgeService,
) {
super(injector);
}
ngOnInit():void {
super.ngOnInit();
@@ -26,9 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit,
} from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { BcfPathHelperService } from 'core-app/features/bim/bcf/helper/bcf-path-helper.service';
@@ -59,6 +57,18 @@ import { JobStatusModalService } from 'core-app/features/job-status/job-status-m
changeDetection: ChangeDetectionStrategy.Default,
})
export class BcfExportButtonComponent extends UntilDestroyedMixin implements OnInit, OnDestroy {
readonly I18n = inject(I18nService);
readonly currentProject = inject(CurrentProjectService);
readonly bcfPathHelper = inject(BcfPathHelperService);
readonly querySpace = inject(IsolatedQuerySpace);
readonly queryUrlParamsHelper = inject(UrlParamsHelperService);
readonly jobStatusModalService = inject(JobStatusModalService);
readonly httpClient = inject(HttpClient);
readonly injector = inject(Injector);
readonly toastService = inject(ToastService);
readonly state = inject(StateService);
readonly cdRef = inject(ChangeDetectorRef);
public text = {
export: this.I18n.t('js.bcf.export'),
export_hover: this.I18n.t('js.bcf.export_bcf_xml_file'),
@@ -68,20 +78,6 @@ export class BcfExportButtonComponent extends UntilDestroyedMixin implements OnI
public exportLink:string;
constructor(readonly I18n:I18nService,
readonly currentProject:CurrentProjectService,
readonly bcfPathHelper:BcfPathHelperService,
readonly querySpace:IsolatedQuerySpace,
readonly queryUrlParamsHelper:UrlParamsHelperService,
readonly jobStatusModalService:JobStatusModalService,
readonly httpClient:HttpClient,
readonly injector:Injector,
readonly toastService:ToastService,
readonly state:StateService,
readonly cdRef:ChangeDetectorRef) {
super();
}
ngOnInit() {
this.querySpace.query
.values$()
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { BcfPathHelperService } from 'core-app/features/bim/bcf/helper/bcf-path-helper.service';
@@ -48,16 +48,15 @@ import { BcfPathHelperService } from 'core-app/features/bim/bcf/helper/bcf-path-
changeDetection: ChangeDetectionStrategy.Default,
})
export class BcfImportButtonComponent {
readonly I18n = inject(I18nService);
readonly currentProject = inject(CurrentProjectService);
readonly bcfPathHelper = inject(BcfPathHelperService);
public text = {
import: this.I18n.t('js.bcf.import'),
import_hover: this.I18n.t('js.bcf.import_bcf_xml_file'),
};
constructor(readonly I18n:I18nService,
readonly currentProject:CurrentProjectService,
readonly bcfPathHelper:BcfPathHelperService) {
}
public handleClick() {
const projectIdentifier = this.currentProject.identifier;
if (projectIdentifier) {
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { StateService } from '@uirouter/core';
@@ -43,15 +43,14 @@ import { StateService } from '@uirouter/core';
standalone: false,
})
export class RefreshButtonComponent {
readonly I18n = inject(I18nService);
readonly state = inject(StateService);
public text = {
refresh: this.I18n.t('js.bcf.refresh'),
refresh_hover: this.I18n.t('js.bcf.refresh_work_package'),
};
constructor(readonly I18n:I18nService,
readonly state:StateService) {
}
refresh() {
void this.state.go('.', {}, { reload: true });
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { IfcModelsDataService } from 'core-app/features/bim/ifc_models/pages/viewer/ifc-models-data.service';
@@ -49,6 +49,9 @@ import { IfcModelsDataService } from 'core-app/features/bim/ifc_models/pages/vie
standalone: false,
})
export class BimManageIfcModelsButtonComponent {
readonly I18n = inject(I18nService);
readonly ifcData = inject(IfcModelsDataService);
text = {
manage: this.I18n.t('js.ifc_models.models.ifc_models'),
};
@@ -56,8 +59,4 @@ export class BimManageIfcModelsButtonComponent {
manageAllowed = this.ifcData.allowed('manage_ifc_models');
manageIFCPath = this.ifcData.manageIFCPath;
constructor(readonly I18n:I18nService,
readonly ifcData:IfcModelsDataService) {
}
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bcf-view.service';
@@ -50,7 +50,8 @@ import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bc
standalone: false,
})
export class BcfViewToggleButtonComponent {
view$ = this.bcfView.live$();
readonly I18n = inject(I18nService);
readonly bcfView = inject(BcfViewService);
constructor(readonly I18n:I18nService, readonly bcfView:BcfViewService) { }
view$ = this.bcfView.live$();
}
@@ -26,8 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service';
import { Directive, ElementRef } from '@angular/core';
import { Directive, inject } from '@angular/core';
import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { StateService } from '@uirouter/core';
@@ -48,15 +47,11 @@ import { OpContextMenuItem } from 'core-app/shared/components/op-context-menu/op
standalone: false,
})
export class BcfViewToggleDropdownDirective extends OpContextMenuTrigger {
constructor(readonly elementRef:ElementRef,
readonly opContextMenu:OPContextMenuService,
readonly bcfView:BcfViewService,
readonly I18n:I18nService,
readonly state:StateService,
readonly wpFiltersService:WorkPackageFiltersService,
readonly viewerBridgeService:ViewerBridgeService) {
super(elementRef, opContextMenu);
}
readonly bcfView = inject(BcfViewService);
readonly I18n = inject(I18nService);
readonly state = inject(StateService);
readonly wpFiltersService = inject(WorkPackageFiltersService);
readonly viewerBridgeService = inject(ViewerBridgeService);
protected open(evt:Event):void {
this.buildItems();
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
@@ -36,13 +36,15 @@ import { I18nService } from 'core-app/core/i18n/i18n.service';
*/
@Injectable()
export class RevitAddInSettingsButtonService {
private readonly i18n = inject(I18nService);
private readonly labelText:string;
private readonly groupLabelText:string;
constructor(
private readonly i18n:I18nService,
) {
constructor() {
const i18n = this.i18n;
const onRevitAddInEnvironment = window.navigator.userAgent.search('Revit') > -1;
if (onRevitAddInEnvironment) {
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import {
distinctUntilChanged, filter, first, map,
@@ -66,8 +66,8 @@ export class RevitBridgeService extends ViewerBridgeService {
revitMessageReceived$ = this.revitMessageReceivedSource.asObservable();
constructor(readonly injector:Injector) {
super(injector);
constructor() {
super();
if (window.RevitBridge) {
this.hookUpRevitListener();
@@ -26,13 +26,10 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectorRef, Directive, ElementRef, Injector,
} from '@angular/core';
import { ChangeDetectorRef, Directive, Injector, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service';
import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive';
import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service';
import { OpModalService } from 'core-app/shared/components/modal/modal.service';
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service';
@@ -43,18 +40,14 @@ import { BoardListComponent } from 'core-app/features/boards/board/board-list/bo
standalone: false,
})
export class AddCardDropdownMenuDirective extends OpContextMenuTrigger {
constructor(readonly elementRef:ElementRef,
readonly opContextMenu:OPContextMenuService,
readonly opModalService:OpModalService,
readonly authorisationService:AuthorisationService,
readonly wpInlineCreate:WorkPackageInlineCreateService,
readonly boardList:BoardListComponent,
readonly injector:Injector,
readonly querySpace:IsolatedQuerySpace,
readonly cdRef:ChangeDetectorRef,
readonly I18n:I18nService) {
super(elementRef, opContextMenu);
}
readonly opModalService = inject(OpModalService);
readonly authorisationService = inject(AuthorisationService);
readonly wpInlineCreate = inject(WorkPackageInlineCreateService);
readonly boardList = inject(BoardListComponent);
readonly injector = inject(Injector);
readonly querySpace = inject(IsolatedQuerySpace);
readonly cdRef = inject(ChangeDetectorRef);
readonly I18n = inject(I18nService);
protected open(evt:Event) {
this.items = this.buildItems();
@@ -26,12 +26,8 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Inject, OnInit, ViewChild,
} from '@angular/core';
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
import { ChangeDetectionStrategy, Component, OnInit, ViewChild, inject } from '@angular/core';
import { OpModalComponent } from 'core-app/shared/components/modal/modal.component';
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { Board } from 'core-app/features/boards/board/board';
import { BoardService } from 'core-app/features/boards/board/board.service';
@@ -58,6 +54,14 @@ import { HalResource } from 'core-app/features/hal/resources/hal-resource';
changeDetection: ChangeDetectionStrategy.Default,
})
export class AddListModalComponent extends OpModalComponent implements OnInit {
readonly boardActions = inject(BoardActionsRegistryService);
readonly halNotification = inject(HalResourceNotificationService);
readonly boardService = inject(BoardService);
readonly I18n = inject(I18nService);
readonly apiV3Service = inject(ApiV3Service);
readonly currentProject = inject(CurrentProjectService);
readonly pathHelper = inject(PathHelperService);
@ViewChild(OpAutocompleterComponent, { static: true }) public ngSelectComponent:OpAutocompleterComponent;
getAutocompleterData = (searchTerm:string):Observable<HalResource[]> => {
@@ -112,19 +116,6 @@ export class AddListModalComponent extends OpModalComponent implements OnInit {
/** Whether the no results warning is displayed */
showWarning = false;
constructor(readonly elementRef:ElementRef,
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
readonly cdRef:ChangeDetectorRef,
readonly boardActions:BoardActionsRegistryService,
readonly halNotification:HalResourceNotificationService,
readonly boardService:BoardService,
readonly I18n:I18nService,
readonly apiV3Service:ApiV3Service,
readonly currentProject:CurrentProjectService,
readonly pathHelper:PathHelperService) {
super(locals, cdRef, elementRef);
}
ngOnInit() {
super.ngOnInit();
this.board = this.locals.board;
@@ -25,7 +25,7 @@
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { UserResource } from 'core-app/features/hal/resources/user-resource';
@@ -41,13 +41,12 @@ import { UserResource } from 'core-app/features/hal/resources/user-resource';
changeDetection: ChangeDetectionStrategy.Default,
})
export class AssigneeBoardHeaderComponent {
readonly pathHelper = inject(PathHelperService);
readonly I18n = inject(I18nService);
@Input('resource') public user:UserResource;
text = {
assignee: this.I18n.t('js.work_packages.properties.assignee'),
};
constructor(readonly pathHelper:PathHelperService,
readonly I18n:I18nService) {
}
}
@@ -7,7 +7,7 @@ import { BoardListsService } from 'core-app/features/boards/board/board-list/boa
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { map } from 'rxjs/operators';
import { IFieldSchema } from 'core-app/shared/components/fields/field.base';
import { WorkPackageChangeset } from 'core-app/features/work-packages/components/wp-edit/work-package-changeset';
@@ -25,15 +25,15 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link';
@Injectable()
export abstract class BoardActionService {
constructor(readonly injector:Injector,
protected boardListsService:BoardListsService,
protected I18n:I18nService,
protected halResourceService:HalResourceService,
protected pathHelper:PathHelperService,
protected currentProject:CurrentProjectService,
protected apiV3Service:ApiV3Service,
protected schemaCache:SchemaCacheService) {
}
readonly injector = inject(Injector);
protected boardListsService = inject(BoardListsService);
protected I18n = inject(I18nService);
protected halResourceService = inject(HalResourceService);
protected pathHelper = inject(PathHelperService);
protected currentProject = inject(CurrentProjectService);
protected apiV3Service = inject(ApiV3Service);
protected schemaCache = inject(SchemaCacheService);
/**
* Get the attribute name
@@ -25,7 +25,7 @@
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { StatusResource } from 'core-app/features/hal/resources/status-resource';
@@ -40,12 +40,11 @@ import { StatusResource } from 'core-app/features/hal/resources/status-resource'
changeDetection: ChangeDetectionStrategy.Default,
})
export class StatusBoardHeaderComponent {
readonly I18n = inject(I18nService);
@Input('resource') public status:StatusResource;
text = {
status: this.I18n.t('js.work_packages.properties.status'),
};
constructor(readonly I18n:I18nService) {
}
}
@@ -25,7 +25,7 @@
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
@@ -42,6 +42,9 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link';
changeDetection: ChangeDetectionStrategy.Default,
})
export class SubprojectBoardHeaderComponent {
readonly pathHelper = inject(PathHelperService);
readonly I18n = inject(I18nService);
@Input() public resource:HalResource;
idFromLink = idFromLink;
@@ -49,8 +52,4 @@ export class SubprojectBoardHeaderComponent {
text = {
project: this.I18n.t('js.label_project'),
};
constructor(readonly pathHelper:PathHelperService,
readonly I18n:I18nService) {
}
}
@@ -25,7 +25,7 @@
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
@@ -43,6 +43,9 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link';
changeDetection: ChangeDetectionStrategy.Default,
})
export class SubtasksBoardHeaderComponent implements OnInit {
readonly pathHelper = inject(PathHelperService);
readonly I18n = inject(I18nService);
@Input() public resource:WorkPackageResource;
idFromLink = idFromLink;
@@ -53,10 +56,6 @@ export class SubtasksBoardHeaderComponent implements OnInit {
typeHighlightingClass:string;
constructor(readonly pathHelper:PathHelperService,
readonly I18n:I18nService) {
}
ngOnInit() {
this.typeHighlightingClass = Highlighting.inlineClass('type', this.resource.type.id!);
}
@@ -25,7 +25,7 @@
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core';
import { VersionResource } from 'core-app/features/hal/resources/version-resource';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
@@ -41,11 +41,11 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service
changeDetection: ChangeDetectionStrategy.Default,
})
export class VersionBoardHeaderComponent {
@Input('resource') public version:VersionResource;
readonly I18n = inject(I18nService);
readonly pathHelper = inject(PathHelperService);
constructor(readonly I18n:I18nService,
readonly pathHelper:PathHelperService) {
}
// eslint-disable-next-line @angular-eslint/no-input-rename
@Input('resource') public version:VersionResource;
public text = {
isLocked: this.I18n.t('js.boards.version.is_locked'),
@@ -1,4 +1,4 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, Input, inject } from '@angular/core';
import { Board } from 'core-app/features/boards/board/board';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { WorkPackageStatesInitializationService } from 'core-app/features/work-packages/components/wp-list/wp-states-initialization.service';
@@ -23,22 +23,20 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
changeDetection: ChangeDetectionStrategy.Default,
})
export class BoardFilterComponent extends UntilDestroyedMixin implements AfterViewInit {
private readonly currentProjectService = inject(CurrentProjectService);
private readonly querySpace = inject(IsolatedQuerySpace);
private readonly apiV3Service = inject(ApiV3Service);
private readonly halResourceService = inject(HalResourceService);
private readonly wpStatesInitialization = inject(WorkPackageStatesInitializationService);
private readonly wpTableFilters = inject(WorkPackageViewFiltersService);
private readonly urlParamsHelper = inject(UrlParamsHelperService);
private readonly boardFilters = inject(BoardFiltersService);
/** Current active */
@Input() public board$:Observable<Board>;
initialized = false;
constructor(private readonly currentProjectService:CurrentProjectService,
private readonly querySpace:IsolatedQuerySpace,
private readonly apiV3Service:ApiV3Service,
private readonly halResourceService:HalResourceService,
private readonly wpStatesInitialization:WorkPackageStatesInitializationService,
private readonly wpTableFilters:WorkPackageViewFiltersService,
private readonly urlParamsHelper:UrlParamsHelperService,
private readonly boardFilters:BoardFiltersService) {
super();
}
ngAfterViewInit():void {
if (!this.board$) {
return;
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import {
WorkPackageInlineCreateService,
@@ -40,13 +40,8 @@ import { Observable } from 'rxjs';
@Injectable()
export class BoardInlineCreateService extends WorkPackageInlineCreateService {
constructor(
readonly injector:Injector,
protected readonly querySpace:IsolatedQuerySpace,
protected readonly halResourceService:HalResourceService,
) {
super(injector);
}
protected readonly querySpace = inject(IsolatedQuerySpace);
protected readonly halResourceService = inject(HalResourceService);
/**
* A separate reference pane for the inline create component
@@ -26,9 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy, Component, EventEmitter, Input, Output,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service';
import { OpModalService } from 'core-app/shared/components/modal/modal.service';
@@ -49,18 +47,17 @@ import { BoardActionService } from 'core-app/features/boards/board/board-actions
changeDetection: ChangeDetectionStrategy.Default,
})
export class BoardListMenuComponent {
readonly opModalService = inject(OpModalService);
readonly authorisationService = inject(AuthorisationService);
private readonly querySpace = inject(IsolatedQuerySpace);
private readonly boardService = inject(BoardService);
private readonly boardActionRegistry = inject(BoardActionsRegistryService);
readonly I18n = inject(I18nService);
@Input() board:Board;
@Output() onRemove = new EventEmitter<void>();
constructor(readonly opModalService:OpModalService,
readonly authorisationService:AuthorisationService,
private readonly querySpace:IsolatedQuerySpace,
private readonly boardService:BoardService,
private readonly boardActionRegistry:BoardActionsRegistryService,
readonly I18n:I18nService) {
}
public get menuItems() {
return async () => {
const items:OpContextMenuItem[] = [
@@ -5,7 +5,6 @@ import {
ElementRef,
EventEmitter,
inject,
Injector,
Input,
OnDestroy,
OnInit,
@@ -19,7 +18,6 @@ import {
import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service';
import { BoardInlineCreateService } from 'core-app/features/boards/board/board-list/board-inline-create.service';
import { AbstractWidgetComponent } from 'core-app/shared/components/grids/widgets/abstract-widget.component';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { Board } from 'core-app/features/boards/board/board';
@@ -89,6 +87,31 @@ export interface DisabledButtonPlaceholder {
standalone: false,
})
export class BoardListComponent extends AbstractWidgetComponent implements OnInit, OnDestroy {
readonly apiv3Service = inject(ApiV3Service);
readonly state = inject(StateService);
readonly cdRef = inject(ChangeDetectorRef);
readonly transitions = inject(TransitionService);
readonly boardFilters = inject(BoardFiltersService);
readonly toastService = inject(ToastService);
readonly querySpace = inject(IsolatedQuerySpace);
readonly halNotification = inject(HalResourceNotificationService);
readonly halEvents = inject(HalEventsService);
readonly wpStatesInitialization = inject(WorkPackageStatesInitializationService);
readonly wpViewFocusService = inject(WorkPackageViewFocusService);
readonly wpViewSelectionService = inject(WorkPackageViewSelectionService);
readonly boardListCrossSelectionService = inject(BoardListCrossSelectionService);
readonly authorisationService = inject(AuthorisationService);
readonly wpInlineCreate = inject(WorkPackageInlineCreateService);
readonly halEditing = inject(HalResourceEditingService);
readonly loadingIndicator = inject(LoadingIndicatorService);
readonly schemaCache = inject(SchemaCacheService);
readonly boardService = inject(BoardService);
readonly boardActionRegistry = inject(BoardActionsRegistryService);
readonly causedUpdates = inject(CausedUpdatesService);
readonly keepTab = inject(KeepTabService);
readonly currentProject = inject(CurrentProjectService);
readonly pathHelper = inject(PathHelperService);
/** Output fired upon query removal */
@Output() onRemove = new EventEmitter<void>();
@@ -127,13 +150,15 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
public columnsQueryProps:any;
public text = {
addCard: this.I18n.t('js.boards.add_card'),
updateSuccessful: this.I18n.t('js.notice_successful_update'),
areYouSure: this.I18n.t('js.text_are_you_sure'),
unnamed_list: this.I18n.t('js.boards.label_unnamed_list'),
click_to_remove: this.I18n.t('js.boards.click_to_remove_list'),
};
public get text() {
return {
addCard: this.i18n.t('js.boards.add_card'),
updateSuccessful: this.i18n.t('js.notice_successful_update'),
areYouSure: this.i18n.t('js.text_are_you_sure'),
unnamed_list: this.i18n.t('js.boards.label_unnamed_list'),
click_to_remove: this.i18n.t('js.boards.click_to_remove_list'),
};
}
/** Are we allowed to remove and drag & drop elements ? */
public canDragInto = false;
@@ -153,37 +178,6 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
private readonly states = inject(States);
constructor(
readonly apiv3Service:ApiV3Service,
readonly I18n:I18nService,
readonly state:StateService,
readonly cdRef:ChangeDetectorRef,
readonly transitions:TransitionService,
readonly boardFilters:BoardFiltersService,
readonly toastService:ToastService,
readonly querySpace:IsolatedQuerySpace,
readonly halNotification:HalResourceNotificationService,
readonly halEvents:HalEventsService,
readonly wpStatesInitialization:WorkPackageStatesInitializationService,
readonly wpViewFocusService:WorkPackageViewFocusService,
readonly wpViewSelectionService:WorkPackageViewSelectionService,
readonly boardListCrossSelectionService:BoardListCrossSelectionService,
readonly authorisationService:AuthorisationService,
readonly wpInlineCreate:WorkPackageInlineCreateService,
readonly injector:Injector,
readonly halEditing:HalResourceEditingService,
readonly loadingIndicator:LoadingIndicatorService,
readonly schemaCache:SchemaCacheService,
readonly boardService:BoardService,
readonly boardActionRegistry:BoardActionsRegistryService,
readonly causedUpdates:CausedUpdatesService,
readonly keepTab:KeepTabService,
readonly currentProject:CurrentProjectService,
readonly pathHelper:PathHelperService,
) {
super(I18n, injector);
}
ngOnInit():void {
// Unset the isNew flag
this.initiallyFocused = this.resource.isNewWidget;
@@ -257,7 +251,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
}
public get errorMessage() {
return this.I18n.t('js.boards.error_loading_the_list', { error_message: this.loadingError });
return this.i18n.t('js.boards.error_loading_the_list', { error_message: this.loadingError });
}
public canMove(workPackage:WorkPackageResource) {
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
@@ -19,18 +19,15 @@ import { IOPFieldSchema } from 'core-app/features/hal/interfaces';
@Injectable({ providedIn: 'root' })
export class BoardListsService {
private readonly CurrentProject = inject(CurrentProjectService);
private readonly pathHelper = inject(PathHelperService);
private readonly apiV3Service = inject(ApiV3Service);
private readonly halResourceService = inject(HalResourceService);
private readonly toastService = inject(ToastService);
private readonly I18n = inject(I18nService);
private v3 = this.pathHelper.api.v3;
constructor(
private readonly CurrentProject:CurrentProjectService,
private readonly pathHelper:PathHelperService,
private readonly apiV3Service:ApiV3Service,
private readonly halResourceService:HalResourceService,
private readonly toastService:ToastService,
private readonly I18n:I18nService) {
}
private create(params:object, filters:ApiV3Filter[]):Observable<QueryResource> {
const filterJson = JSON.stringify(filters);
@@ -26,14 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Injector,
Input,
OnDestroy,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, Injector, Input, OnDestroy, inject } from '@angular/core';
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
import {
WorkPackageIsolatedQuerySpaceDirective,
@@ -64,12 +57,14 @@ import { QueryUpdatedService } from 'core-app/features/boards/board/query-update
standalone: false,
})
export class BoardEntryComponent implements OnDestroy {
readonly elementRef = inject(ElementRef);
readonly injector = inject(Injector);
@Input() boardId:string;
constructor(
readonly elementRef:ElementRef,
readonly injector:Injector,
) {
constructor() {
const injector = this.injector;
populateInputsFromDataset(this);
document.body.classList.add('router--boards-full-view');
@@ -54,6 +54,24 @@ import { resolveRoutingId } from 'core-app/features/work-packages/helpers/work-p
standalone: false,
})
export class BoardListContainerComponent extends UntilDestroyedMixin implements OnInit {
readonly I18n = inject(I18nService);
readonly state = inject(StateService);
readonly toastService = inject(ToastService);
readonly halNotification = inject(HalResourceNotificationService);
readonly boardComponent = inject(BoardPartitionedPageComponent);
readonly BoardList = inject(BoardListsService);
readonly boardActionRegistry = inject(BoardActionsRegistryService);
readonly opModalService = inject(OpModalService);
readonly injector = inject(Injector);
readonly apiV3Service = inject(ApiV3Service);
readonly Boards = inject(BoardService);
readonly boardListCrossSelectionService = inject(BoardListCrossSelectionService);
readonly Drag = inject(DragAndDropService);
readonly apiv3Service = inject(ApiV3Service);
readonly QueryUpdated = inject(QueryUpdatedService);
readonly pathHelper = inject(PathHelperService);
readonly currentProject = inject(CurrentProjectService);
@Input() boardId:string;
text = {
delete: this.I18n.t('js.button_delete'),
@@ -96,28 +114,6 @@ export class BoardListContainerComponent extends UntilDestroyedMixin implements
private readonly wpStates = inject(States);
constructor(
readonly I18n:I18nService,
readonly state:StateService,
readonly toastService:ToastService,
readonly halNotification:HalResourceNotificationService,
readonly boardComponent:BoardPartitionedPageComponent,
readonly BoardList:BoardListsService,
readonly boardActionRegistry:BoardActionsRegistryService,
readonly opModalService:OpModalService,
readonly injector:Injector,
readonly apiV3Service:ApiV3Service,
readonly Boards:BoardService,
readonly boardListCrossSelectionService:BoardListCrossSelectionService,
readonly Drag:DragAndDropService,
readonly apiv3Service:ApiV3Service,
readonly QueryUpdated:QueryUpdatedService,
readonly pathHelper:PathHelperService,
readonly currentProject:CurrentProjectService,
) {
super();
}
ngOnInit():void {
const id:string = this.boardId || this.state.params.board_id?.toString();
this.board$ = this
@@ -1,11 +1,4 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Injector,
Input,
OnInit,
} from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, Input, OnInit, inject } from '@angular/core';
import {
DynamicComponentDefinition,
ToolbarButtonComponentDefinition,
@@ -60,6 +53,20 @@ export function boardCardViewHandlerFactory(injector:Injector) {
standalone: false,
})
export class BoardPartitionedPageComponent extends UntilDestroyedMixin implements OnInit {
readonly I18n = inject(I18nService);
readonly cdRef = inject(ChangeDetectorRef);
readonly state = inject(StateService);
readonly toastService = inject(ToastService);
readonly halNotification = inject(HalResourceNotificationService);
readonly injector = inject(Injector);
readonly apiV3Service = inject(ApiV3Service);
readonly boardFilters = inject(BoardFiltersService);
readonly Boards = inject(BoardService);
readonly titleService = inject(OpTitleService);
readonly submenuService = inject(SubmenuService);
readonly pathHelperService = inject(PathHelperService);
readonly currentProject = inject(CurrentProjectService);
@Input() boardId:string;
text = {
button_more: this.I18n.t('js.button_more'),
@@ -129,24 +136,6 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement
},
];
constructor(
readonly I18n:I18nService,
readonly cdRef:ChangeDetectorRef,
readonly state:StateService,
readonly toastService:ToastService,
readonly halNotification:HalResourceNotificationService,
readonly injector:Injector,
readonly apiV3Service:ApiV3Service,
readonly boardFilters:BoardFiltersService,
readonly Boards:BoardService,
readonly titleService:OpTitleService,
readonly submenuService:SubmenuService,
readonly pathHelperService:PathHelperService,
readonly currentProject:CurrentProjectService,
) {
super();
}
ngOnInit():void {
// Ensure board is being loaded
this.Boards.loadAllBoards();
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
@@ -10,6 +10,12 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
@Injectable({ providedIn: 'root' })
export class BoardService {
protected apiV3Service = inject(ApiV3Service);
protected PathHelper = inject(PathHelperService);
protected CurrentProject = inject(CurrentProjectService);
protected halResourceService = inject(HalResourceService);
protected I18n = inject(I18nService);
public currentBoard$:BehaviorSubject<string|null> = new BehaviorSubject<string|null>(null);
private loadAllPromise:Promise<Board[]>|undefined;
@@ -21,15 +27,6 @@ export class BoardService {
unnamed_list: this.I18n.t('js.boards.label_unnamed_list'),
};
constructor(
protected apiV3Service:ApiV3Service,
protected PathHelper:PathHelperService,
protected CurrentProject:CurrentProjectService,
protected halResourceService:HalResourceService,
protected I18n:I18nService,
) {
}
/**
* Return all boards in the current scope of the project
*
@@ -1,19 +1,5 @@
import {
ApplicationRef,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ComponentFactoryResolver,
ElementRef,
Inject,
Injector,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
import { ApplicationRef, ChangeDetectionStrategy, Component, ComponentFactoryResolver, ElementRef, Injector, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { OpModalComponent } from 'core-app/shared/components/modal/modal.component';
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
import {
ActiveTabInterface,
TabComponent,
@@ -34,6 +20,13 @@ import { Board } from 'core-app/features/boards/board/board';
changeDetection: ChangeDetectionStrategy.Default,
})
export class BoardConfigurationModalComponent extends OpModalComponent implements OnInit, OnDestroy {
readonly I18n = inject(I18nService);
readonly boardService = inject(BoardService);
readonly boardConfigurationService = inject(BoardConfigurationService);
readonly injector = inject(Injector);
readonly appRef = inject(ApplicationRef);
readonly componentFactoryResolver = inject(ComponentFactoryResolver);
public text = {
title: this.I18n.t('js.boards.configuration_modal.title'),
closePopup: this.I18n.t('js.close_popup_title'),
@@ -48,18 +41,6 @@ export class BoardConfigurationModalComponent extends OpModalComponent implement
// And a reference to the actual portal host interface
public tabPortalHost:TabPortalOutlet;
constructor(@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
readonly I18n:I18nService,
readonly boardService:BoardService,
readonly boardConfigurationService:BoardConfigurationService,
readonly injector:Injector,
readonly appRef:ApplicationRef,
readonly componentFactoryResolver:ComponentFactoryResolver,
readonly cdRef:ChangeDetectorRef,
readonly elementRef:ElementRef) {
super(locals, cdRef, elementRef);
}
ngOnInit() {
this.element = this.elementRef.nativeElement as HTMLElement;
@@ -1,10 +1,12 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { TabInterface } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet';
import { BoardHighlightingTabComponent } from 'core-app/features/boards/board/configuration-modal/tabs/highlighting-tab.component';
@Injectable()
export class BoardConfigurationService {
readonly I18n = inject(I18nService);
protected _tabs:TabInterface[] = [
{
id: 'highlighting',
@@ -13,9 +15,6 @@ export class BoardConfigurationService {
},
];
constructor(readonly I18n:I18nService) {
}
public get tabs() {
return this._tabs;
}
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Inject, Injector, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Injector, OnInit, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { TabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet';
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
@@ -15,6 +15,10 @@ import { CardHighlightingMode } from 'core-app/features/work-packages/components
changeDetection: ChangeDetectionStrategy.Default,
})
export class BoardHighlightingTabComponent implements TabComponent, OnInit {
readonly injector = inject(Injector);
locals = inject<OpModalLocalsMap>(OpModalLocalsToken);
readonly I18n = inject(I18nService);
// Highlighting mode
public highlightingMode:CardHighlightingMode = 'none';
@@ -35,11 +39,6 @@ export class BoardHighlightingTabComponent implements TabComponent, OnInit {
},
};
constructor(readonly injector:Injector,
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
readonly I18n:I18nService) {
}
public onSave() {
this.updateMode(this.highlightingMode);
this.board.highlightingMode = this.highlightingMode;
@@ -26,17 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
Output,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
@@ -66,6 +56,18 @@ import { HalResourceService } from 'core-app/features/hal/services/hal-resource.
changeDetection: ChangeDetectionStrategy.Default,
})
export class BoardInlineAddAutocompleterComponent implements AfterViewInit {
private readonly querySpace = inject(IsolatedQuerySpace);
private readonly pathHelper = inject(PathHelperService);
private readonly apiV3Service = inject(ApiV3Service);
private readonly urlParamsHelper = inject(UrlParamsHelperService);
private readonly notificationService = inject(WorkPackageNotificationService);
private readonly CurrentProject = inject(CurrentProjectService);
private readonly halResourceService = inject(HalResourceService);
private readonly schemaCacheService = inject(SchemaCacheService);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly I18n = inject(I18nService);
private readonly wpCardDragDrop = inject(WorkPackageCardDragAndDropService);
readonly text = {
placeholder: this.I18n.t('js.relations_autocomplete.placeholder'),
};
@@ -119,19 +121,6 @@ export class BoardInlineAddAutocompleterComponent implements AfterViewInit {
@Output() onReferenced = new EventEmitter<WorkPackageResource>();
constructor(private readonly querySpace:IsolatedQuerySpace,
private readonly pathHelper:PathHelperService,
private readonly apiV3Service:ApiV3Service,
private readonly urlParamsHelper:UrlParamsHelperService,
private readonly notificationService:WorkPackageNotificationService,
private readonly CurrentProject:CurrentProjectService,
private readonly halResourceService:HalResourceService,
private readonly schemaCacheService:SchemaCacheService,
private readonly cdRef:ChangeDetectorRef,
private readonly I18n:I18nService,
private readonly wpCardDragDrop:WorkPackageCardDragAndDropService) {
}
ngAfterViewInit():void {
if (!this.ngSelectComponent.ngSelectInstance) {
return;
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { interval } from 'rxjs';
import { filter, startWith, switchMap } from 'rxjs/operators';
import { ActiveWindowService } from 'core-app/core/active-window/active-window.service';
@@ -8,9 +8,9 @@ const POLLING_INTERVAL = 2000;
@Injectable()
export class QueryUpdatedService {
constructor(readonly activeWindow:ActiveWindowService,
readonly apiV3Service:ApiV3Service) {
}
readonly activeWindow = inject(ActiveWindowService);
readonly apiV3Service = inject(ApiV3Service);
public monitor(ids:string[]) {
let time = new Date();
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { Board } from 'core-app/features/boards/board/board';
import { Observable } from 'rxjs';
@@ -19,12 +19,11 @@ import { Observable } from 'rxjs';
changeDetection: ChangeDetectionStrategy.Default,
})
export class BoardsMenuButtonComponent {
readonly I18n = inject(I18nService);
@Input() board$:Observable<Board>;
text = {
button_more: this.I18n.t('js.button_more'),
};
constructor(readonly I18n:I18nService) {
}
}
@@ -26,13 +26,10 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
Directive, ElementRef, Injector, Input,
} from '@angular/core';
import { Directive, Injector, Input, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive';
import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service';
import { OpModalService } from 'core-app/shared/components/modal/modal.service';
import { Board } from 'core-app/features/boards/board/board';
import { BoardConfigurationModalComponent } from 'core-app/features/boards/board/configuration-modal/board-configuration.modal';
@@ -46,21 +43,16 @@ import { selectableTitleIdentifier, triggerEditingEvent } from 'core-app/shared/
standalone: false,
})
export class BoardsToolbarMenuDirective extends OpContextMenuTrigger {
@Input('boardsToolbarMenu-resource') public board:Board;
readonly opModalService = inject(OpModalService);
readonly boardService = inject(BoardService);
readonly Notifications = inject(ToastService);
readonly State = inject(StateService);
readonly injector = inject(Injector);
readonly I18n = inject(I18nService);
readonly http = inject(HttpClient);
constructor(
readonly elementRef:ElementRef,
readonly opContextMenu:OPContextMenuService,
readonly opModalService:OpModalService,
readonly boardService:BoardService,
readonly Notifications:ToastService,
readonly State:StateService,
readonly injector:Injector,
readonly I18n:I18nService,
readonly http:HttpClient,
) {
super(elementRef, opContextMenu);
}
// eslint-disable-next-line @angular-eslint/no-input-rename
@Input('boardsToolbarMenu-resource') public board:Board;
public get locals() {
return {
@@ -26,13 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnDestroy,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, inject } from '@angular/core';
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
import {
WorkPackageIsolatedQuerySpaceDirective,
@@ -45,9 +39,11 @@ import {
standalone: false,
})
export class CalendarEntryComponent implements OnDestroy {
readonly elementRef = inject(ElementRef);
@Input() queryId:string;
constructor(readonly elementRef:ElementRef) {
constructor() {
populateInputsFromDataset(this);
document.body.classList.add('router--calendar');
}
@@ -26,16 +26,25 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { TestBed } from '@angular/core/testing';
import { OpCalendarService } from 'core-app/features/calendar/op-calendar.service';
import { WeekdayService } from 'core-app/core/days/weekday.service';
import { DayResourceService } from 'core-app/core/state/days/day.service';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
describe('OP calendar service', () => {
let service:OpCalendarService;
beforeEach(() => {
// This is not a valid constructor call, but since we only want to test a helper method that does not
// depend on injected services, we can pass null values here.
// @ts-expect-error ignore invalid constructor call since we don't need a completely valid instance
service = new OpCalendarService(null, null, null);
TestBed.configureTestingModule({
providers: [
OpCalendarService,
{ provide: WeekdayService, useValue: {} },
{ provide: DayResourceService, useValue: {} },
{ provide: ConfigurationService, useValue: {} },
],
});
service = TestBed.inject(OpCalendarService);
});
describe('stripYearFromDateFormat', () => {
@@ -1,7 +1,4 @@
import {
ElementRef,
Injectable,
} from '@angular/core';
import { ElementRef, Injectable, inject } from '@angular/core';
import { Subject } from 'rxjs';
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin';
import { WeekdayService } from 'core-app/core/days/weekday.service';
@@ -13,18 +10,14 @@ import { DayHeaderContentArg } from '@fullcalendar/core';
@Injectable()
export class OpCalendarService extends UntilDestroyedMixin {
readonly weekdayService = inject(WeekdayService);
readonly dayService = inject(DayResourceService);
readonly configurationService = inject(ConfigurationService);
resize$ = new Subject<void>();
resizeObs:ResizeObserver;
constructor(
readonly weekdayService:WeekdayService,
readonly dayService:DayResourceService,
readonly configurationService:ConfigurationService,
) {
super();
}
resizeObserver(v:ElementRef|undefined):void {
if (!v) {
return;
@@ -75,6 +75,28 @@ interface CalendarOptionsWithDayGrid extends CalendarOptions {
@Injectable()
export class OpWorkPackagesCalendarService extends UntilDestroyedMixin {
private I18n = inject(I18nService);
private configuration = inject(ConfigurationService);
private sanitizer = inject(DomSanitizer);
readonly injector = inject(Injector);
readonly schemaCache = inject(SchemaCacheService);
readonly toastService = inject(ToastService);
readonly wpTableFilters = inject(WorkPackageViewFiltersService);
readonly wpListService = inject(WorkPackagesListService);
readonly wpListChecksumService = inject(WorkPackagesListChecksumService);
readonly urlParamsHelper = inject(UrlParamsHelperService);
readonly querySpace = inject(IsolatedQuerySpace);
readonly apiV3Service = inject(ApiV3Service);
readonly halResourceService = inject(HalResourceService);
readonly timezoneService = inject(TimezoneService);
readonly pathHelper = inject(PathHelperService);
readonly halEditing = inject(HalResourceEditingService);
readonly wpTableSelection = inject(WorkPackageViewSelectionService);
readonly contextMenuService = inject(OPContextMenuService);
readonly calendarService = inject(OpCalendarService);
readonly weekdayService = inject(WeekdayService);
readonly dayService = inject(DayResourceService);
static MAX_DISPLAYED = 500;
tooManyResultsText:string|null;
@@ -91,32 +113,6 @@ export class OpWorkPackagesCalendarService extends UntilDestroyedMixin {
private readonly states = inject(States);
constructor(
private I18n:I18nService,
private configuration:ConfigurationService,
private sanitizer:DomSanitizer,
readonly injector:Injector,
readonly schemaCache:SchemaCacheService,
readonly toastService:ToastService,
readonly wpTableFilters:WorkPackageViewFiltersService,
readonly wpListService:WorkPackagesListService,
readonly wpListChecksumService:WorkPackagesListChecksumService,
readonly urlParamsHelper:UrlParamsHelperService,
readonly querySpace:IsolatedQuerySpace,
readonly apiV3Service:ApiV3Service,
readonly halResourceService:HalResourceService,
readonly timezoneService:TimezoneService,
readonly pathHelper:PathHelperService,
readonly halEditing:HalResourceEditingService,
readonly wpTableSelection:WorkPackageViewSelectionService,
readonly contextMenuService:OPContextMenuService,
readonly calendarService:OpCalendarService,
readonly weekdayService:WeekdayService,
readonly dayService:DayResourceService,
) {
super();
}
calendarOptions(additionalOptions:CalendarOptions):CalendarOptions {
return { ...this.defaultOptions(), ...additionalOptions };
}
@@ -1,17 +1,4 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Injector,
Input,
OnDestroy,
Output,
SecurityContext,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Injector, Input, OnDestroy, Output, SecurityContext, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import { FullCalendarComponent } from '@fullcalendar/angular';
import { States } from 'core-app/core/states/states.service';
import moment, { Moment } from 'moment';
@@ -115,6 +102,25 @@ const ADD_ENTRY_PROHIBITED_CLASS_NAME = '-prohibited';
standalone: false,
})
export class TimeEntryCalendarComponent implements AfterViewInit, OnDestroy {
readonly states = inject(States);
readonly apiV3Service = inject(ApiV3Service);
readonly $state = inject(StateService);
private element = inject(ElementRef);
readonly i18n = inject(I18nService);
readonly injector = inject(Injector);
readonly notifications = inject(HalResourceNotificationService);
private sanitizer = inject(DomSanitizer);
private configuration = inject(ConfigurationService);
private timezone = inject(TimezoneService);
private schemaCache = inject(SchemaCacheService);
private colors = inject(ColorsService);
private browserDetector = inject(BrowserDetector);
private calendar = inject(OpCalendarService);
readonly weekdayService = inject(WeekdayService);
readonly dayService = inject(DayResourceService);
readonly turboRequests = inject(TurboRequestsService);
readonly pathHelper = inject(PathHelperService);
@ViewChild(FullCalendarComponent) ucCalendar:FullCalendarComponent;
@Input() projectIdentifier:string;
@@ -202,27 +208,6 @@ export class TimeEntryCalendarComponent implements AfterViewInit, OnDestroy {
});
}
constructor(
readonly states:States,
readonly apiV3Service:ApiV3Service,
readonly $state:StateService,
private element:ElementRef,
readonly i18n:I18nService,
readonly injector:Injector,
readonly notifications:HalResourceNotificationService,
private sanitizer:DomSanitizer,
private configuration:ConfigurationService,
private timezone:TimezoneService,
private schemaCache:SchemaCacheService,
private colors:ColorsService,
private browserDetector:BrowserDetector,
private calendar:OpCalendarService,
readonly weekdayService:WeekdayService,
readonly dayService:DayResourceService,
readonly turboRequests:TurboRequestsService,
readonly pathHelper:PathHelperService,
) { }
ngAfterViewInit():void {
document.addEventListener('dialog:close', this.closeDialogHandler);
}
@@ -26,15 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnInit,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import {
CalendarOptions,
DateSelectArg,
@@ -103,6 +95,29 @@ import { TimezoneService } from 'core-app/core/datetime/timezone.service';
standalone: false,
})
export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implements OnInit {
readonly actions$ = inject(ActionsService);
readonly states = inject(States);
readonly wpTableFilters = inject(WorkPackageViewFiltersService);
readonly wpListService = inject(WorkPackagesListService);
readonly querySpace = inject(IsolatedQuerySpace);
readonly schemaCache = inject(SchemaCacheService);
private element = inject(ElementRef);
readonly i18n = inject(I18nService);
readonly toastService = inject(ToastService);
private sanitizer = inject(DomSanitizer);
private I18n = inject(I18nService);
private configuration = inject(ConfigurationService);
readonly calendar = inject(OpCalendarService);
readonly workPackagesCalendar = inject(OpWorkPackagesCalendarService);
readonly currentProject = inject(CurrentProjectService);
readonly halEditing = inject(HalResourceEditingService);
readonly halNotification = inject(HalResourceNotificationService);
readonly weekdayService = inject(WeekdayService);
readonly dayService = inject(DayResourceService);
readonly apiV3Service = inject(ApiV3Service);
readonly pathHelper = inject(PathHelperService);
readonly timezoneService = inject(TimezoneService);
@ViewChild(FullCalendarComponent) ucCalendar:FullCalendarComponent;
@ViewChild('ucCalendar', { read: ElementRef })
@@ -123,33 +138,6 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement
today: this.I18n.t('js.team_planner.today'),
};
constructor(
readonly actions$:ActionsService,
readonly states:States,
readonly wpTableFilters:WorkPackageViewFiltersService,
readonly wpListService:WorkPackagesListService,
readonly querySpace:IsolatedQuerySpace,
readonly schemaCache:SchemaCacheService,
private element:ElementRef,
readonly i18n:I18nService,
readonly toastService:ToastService,
private sanitizer:DomSanitizer,
private I18n:I18nService,
private configuration:ConfigurationService,
readonly calendar:OpCalendarService,
readonly workPackagesCalendar:OpWorkPackagesCalendarService,
readonly currentProject:CurrentProjectService,
readonly halEditing:HalResourceEditingService,
readonly halNotification:HalResourceNotificationService,
readonly weekdayService:WeekdayService,
readonly dayService:DayResourceService,
readonly apiV3Service:ApiV3Service,
readonly pathHelper:PathHelperService,
readonly timezoneService:TimezoneService,
) {
super();
}
ngOnInit():void {
registerEffectCallbacks(this, this.untilDestroyed());
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { BannersService } from 'core-app/core/enterprise/banners.service';
@@ -37,6 +37,9 @@ import { BannersService } from 'core-app/core/enterprise/banners.service';
standalone: false,
})
export class EnterpriseBannerFrameComponent implements OnInit {
protected pathHelper = inject(PathHelperService);
protected banners = inject(BannersService);
@Input() public feature:string;
@Input() public dismissable = false;
@@ -45,12 +48,6 @@ export class EnterpriseBannerFrameComponent implements OnInit {
frameURL:string;
frameID:string;
constructor(
protected pathHelper:PathHelperService,
protected banners:BannersService,
) {
}
ngOnInit() {
this.visible = this.banners.showBannerFor(this.feature);
this.frameURL = this.pathHelper.bannerFramePath(this.feature, this.dismissable);
@@ -1,7 +1,4 @@
import {
ErrorHandler,
Injectable,
} from '@angular/core';
import { ErrorHandler, Injectable, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { ErrorResource } from 'core-app/features/hal/resources/error-resource';
@@ -14,14 +11,12 @@ interface RejectedPromise {
@Injectable()
export class HalAwareErrorHandler extends ErrorHandler {
private readonly I18n = inject(I18nService);
private text = {
internal_error: this.I18n.t('js.error.internal'),
};
constructor(private readonly I18n:I18nService) {
super();
}
public handleError(error:unknown):void {
let message:string = this.text.internal_error;
@@ -28,7 +28,7 @@
import { StateService } from '@uirouter/core';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { LoadingIndicatorService } from 'core-app/core/loading-indicator/loading-indicator.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
@@ -41,6 +41,8 @@ import { HalError } from 'core-app/features/hal/services/hal-error';
@Injectable()
export class HalResourceNotificationService {
injector = inject(Injector);
@InjectField() protected I18n:I18nService;
@InjectField() protected $state:StateService;
@@ -53,9 +55,6 @@ export class HalResourceNotificationService {
@InjectField() protected schemaCache:SchemaCacheService;
constructor(public injector:Injector) {
}
public showSave(resource:HalResource, isCreate = false) {
const message:any = {
message: this.I18n.t(`js.notice_successful_${isCreate ? 'create' : 'update'}`),
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { catchError, map } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';
@@ -59,17 +59,14 @@ interface ErrorWithType {
@Injectable({ providedIn: 'root' })
export class HalResourceService {
readonly injector = inject(Injector);
readonly http = inject(HttpClient);
/**
* List of all known hal resources, extendable.
*/
private config:Record<string, HalResourceFactoryConfigInterface> = {};
constructor(
readonly injector:Injector,
readonly http:HttpClient,
) {
}
/**
* Perform a HTTP request and return a HalResource promise.
*/
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, HostListener } from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, HostListener, inject } from '@angular/core';
import { combineLatest, merge, Observable, timer } from 'rxjs';
import { filter, map, shareReplay, switchMap, throttleTime } from 'rxjs/operators';
import { ActiveWindowService } from 'core-app/core/active-window/active-window.service';
@@ -15,6 +15,12 @@ import { populateInputsFromDataset } from 'core-app/shared/components/dataset-in
standalone: false,
})
export class InAppNotificationBellComponent implements OnInit {
readonly elementRef = inject(ElementRef);
readonly storeService = inject(IanBellService);
readonly apiV3Service = inject(ApiV3Service);
readonly activeWindow = inject(ActiveWindowService);
readonly pathHelper = inject(PathHelperService);
@Input() interval = 50000;
polling$:Observable<number>;
@@ -25,13 +31,7 @@ export class InAppNotificationBellComponent implements OnInit {
public bellDisplayLimit = 99;
constructor(
readonly elementRef:ElementRef,
readonly storeService:IanBellService,
readonly apiV3Service:ApiV3Service,
readonly activeWindow:ActiveWindowService,
readonly pathHelper:PathHelperService,
) {
constructor() {
populateInputsFromDataset(this);
}
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import {
catchError,
map,
@@ -57,6 +57,9 @@ import { IanBellStore } from 'core-app/features/in-app-notifications/bell/state/
@Injectable({ providedIn: 'root' })
@EffectHandler
export class IanBellService {
readonly actions$ = inject(ActionsService);
readonly resourceService = inject(InAppNotificationsResourceService);
readonly id = 'ian-bell';
readonly store = new IanBellStore();
@@ -65,10 +68,7 @@ export class IanBellService {
unread$ = this.query.unread$;
constructor(
readonly actions$:ActionsService,
readonly resourceService:InAppNotificationsResourceService,
) {
constructor() {
this.query.unreadCountChanged$.subscribe((count) => {
this.actions$.dispatch(notificationCountChanged({ origin: this.id, count }));
});
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { filter, map } from 'rxjs/operators';
import { StateService } from '@uirouter/angular';
@@ -52,6 +52,17 @@ import {
standalone: false,
})
export class InAppNotificationCenterComponent implements OnInit {
readonly cdRef = inject(ChangeDetectorRef);
readonly elementRef = inject(ElementRef);
readonly I18n = inject(I18nService);
readonly storeService = inject(IanCenterService);
readonly bellService = inject(IanBellService);
readonly urlParams = inject(UrlParamsService);
readonly state = inject(StateService);
readonly apiV3 = inject(ApiV3Service);
readonly pathService = inject(PathHelperService);
readonly colorsService = inject(ColorsService);
maxSize = NOTIFICATIONS_MAX_SIZE;
hasMoreThanPageSize$ = this.storeService.hasMoreThanPageSize$;
@@ -141,20 +152,6 @@ export class InAppNotificationCenterComponent implements OnInit {
protected readonly idFromLink = idFromLink;
constructor(
readonly cdRef:ChangeDetectorRef,
readonly elementRef:ElementRef,
readonly I18n:I18nService,
readonly storeService:IanCenterService,
readonly bellService:IanBellService,
readonly urlParams:UrlParamsService,
readonly state:StateService,
readonly apiV3:ApiV3Service,
readonly pathService:PathHelperService,
readonly colorsService:ColorsService,
) {
}
ngOnInit():void {
const facet = this.urlParams.get('facet') || 'unread';
this.storeService.setFacet(facet as 'unread'|'all');
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, inject } from '@angular/core';
import { debounceTime, defaultIfEmpty, distinctUntilChanged, map, mapTo, switchMap, take, tap } from 'rxjs/operators';
import { forkJoin, from, Observable, Subject } from 'rxjs';
import { ID, Query } from '@datorama/akita';
@@ -71,6 +71,18 @@ export interface INotificationPageQueryParameters {
@Injectable({ providedIn: 'root' })
@EffectHandler
export class IanCenterService extends UntilDestroyedMixin {
readonly I18n = inject(I18nService);
readonly injector = inject(Injector);
readonly resourceService = inject(InAppNotificationsResourceService);
readonly actions$ = inject(ActionsService);
readonly apiV3Service = inject(ApiV3Service);
readonly toastService = inject(ToastService);
readonly urlParams = inject(UrlParamsService);
readonly state = inject(StateService);
readonly deviceService = inject(DeviceService);
readonly pathHelper = inject(PathHelperService);
readonly ianBellService = inject(IanBellService);
readonly id = 'ian-center';
readonly store = new IanCenterStore();
@@ -182,19 +194,7 @@ export class IanCenterService extends UntilDestroyedMixin {
selectedWorkPackage$ = this.urlParams.pathMatching$(/\/details\/(\d+)/);
constructor(
readonly I18n:I18nService,
readonly injector:Injector,
readonly resourceService:InAppNotificationsResourceService,
readonly actions$:ActionsService,
readonly apiV3Service:ApiV3Service,
readonly toastService:ToastService,
readonly urlParams:UrlParamsService,
readonly state:StateService,
readonly deviceService:DeviceService,
readonly pathHelper:PathHelperService,
readonly ianBellService:IanBellService,
) {
constructor() {
super();
this.reload.subscribe();
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation, inject } from '@angular/core';
import { DeviceService } from 'core-app/core/browser/device.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
@@ -13,6 +13,9 @@ import { PrincipalLike } from 'core-app/shared/components/principal/principal-ty
standalone: false,
})
export class InAppNotificationActorsLineComponent implements OnInit {
readonly deviceService = inject(DeviceService);
private I18n = inject(I18nService);
@HostBinding('class.op-ian-actors') className = true;
@Input() aggregatedNotifications:INotification[];
@@ -34,11 +37,6 @@ export class InAppNotificationActorsLineComponent implements OnInit {
mark_as_read: this.I18n.t('js.notifications.center.mark_as_read'),
};
constructor(
readonly deviceService:DeviceService,
private I18n:I18nService,
) { }
ngOnInit():void {
// Don't show the actor if the first item is actor-less (date alert)
if (this.notification._links.actor) {
@@ -1,11 +1,4 @@
import {
ChangeDetectionStrategy,
Component,
HostBinding,
Input,
OnInit,
ViewEncapsulation,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation, inject } from '@angular/core';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { IInAppNotificationDetailsAttribute, INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
@@ -20,6 +13,9 @@ import moment, { Moment } from 'moment';
standalone: false,
})
export class InAppNotificationDateAlertComponent implements OnInit {
private I18n = inject(I18nService);
private timezoneService = inject(TimezoneService);
@Input() aggregatedNotifications:INotification[];
@HostBinding('class.op-ian-date-alert') className = true;
@@ -49,11 +45,6 @@ export class InAppNotificationDateAlertComponent implements OnInit {
note: '', // date alerts do not have notes
};
constructor(
private I18n:I18nService,
private timezoneService:TimezoneService,
) { }
ngOnInit():void {
// Find the most important date alert
const interestingAlert = this.deriveMostRelevantAlert(this.aggregatedNotifications);
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation, inject } from '@angular/core';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@@ -23,6 +23,14 @@ import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destr
standalone: false,
})
export class InAppNotificationEntryComponent extends UntilDestroyedMixin implements OnInit {
readonly apiV3Service = inject(ApiV3Service);
readonly I18n = inject(I18nService);
readonly storeService = inject(IanCenterService);
readonly timezoneService = inject(TimezoneService);
readonly pathHelper = inject(PathHelperService);
readonly deviceService = inject(DeviceService);
readonly urlParams = inject(UrlParamsService);
@HostBinding('class.op-ian-item') className = true;
@Input() notification:INotification;
@@ -55,18 +63,6 @@ export class InAppNotificationEntryComponent extends UntilDestroyedMixin impleme
workPackageId:string|null;
constructor(
readonly apiV3Service:ApiV3Service,
readonly I18n:I18nService,
readonly storeService:IanCenterService,
readonly timezoneService:TimezoneService,
readonly pathHelper:PathHelperService,
readonly deviceService:DeviceService,
readonly urlParams:UrlParamsService,
) {
super();
}
ngOnInit():void {
const href = this.notification._links.resource?.href;
this.workPackageId = href && HalResource.matchFromLink(href, 'work_packages');
@@ -1,10 +1,4 @@
import {
ChangeDetectionStrategy,
Component,
Input,
OnInit,
ViewEncapsulation,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation, inject } from '@angular/core';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
@@ -20,6 +14,9 @@ import { distinctUntilChanged, map } from 'rxjs/operators';
standalone: false,
})
export class InAppNotificationRelativeTimeComponent implements OnInit {
private I18n = inject(I18nService);
private timezoneService = inject(TimezoneService);
@Input() notification:INotification;
@Input() hasActorByLine = true;
@@ -37,11 +34,6 @@ export class InAppNotificationRelativeTimeComponent implements OnInit {
),
};
constructor(
private I18n:I18nService,
private timezoneService:TimezoneService,
) { }
ngOnInit():void {
this.buildTime();
}
@@ -1,11 +1,4 @@
import {
ChangeDetectionStrategy,
Component,
HostBinding,
Input,
OnInit,
ViewEncapsulation,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { IInAppNotificationDetailsResource, INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
@@ -18,6 +11,8 @@ import { IInAppNotificationDetailsResource, INotification } from 'core-app/core/
standalone: false,
})
export class InAppNotificationReminderAlertComponent implements OnInit {
private I18n = inject(I18nService);
@Input() aggregatedNotifications:INotification[];
@HostBinding('class.op-ian-reminder-alert') className = true;
@@ -27,10 +22,6 @@ export class InAppNotificationReminderAlertComponent implements OnInit {
hasDateAlert = false;
dateAlerts:INotification[] = [];
constructor(
private I18n:I18nService,
) { }
ngOnInit():void {
this.reminderAlert = this.deriveMostRecentReminder(this.aggregatedNotifications);
this.reminderNote = this.extractReminderNoteValue(this.reminderAlert._embedded.details);
@@ -1,13 +1,12 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
@Injectable({ providedIn: 'root' })
export class JobStatusModalService {
constructor(
protected pathHelper:PathHelperService,
protected turboRequests:TurboRequestsService,
) {}
protected pathHelper = inject(PathHelperService);
protected turboRequests = inject(TurboRequestsService);
public show(jobId:string):void {
void this.turboRequests.requestStream(this.jobModalUrl(jobId));

Some files were not shown because too many files have changed in this diff Show More