From e5f83ff08605424b6656d7b379fd1d95f3f4d1b5 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 17 Jan 2025 16:42:31 +0100 Subject: [PATCH] [PM-17031] Create UI-common (#12831) Extract core functionality from `libs/angular` to allow teams to depend on `libs/ui-common` instead. Moves the following functionality to `ui-common`. - `I18nPipe`. `libs/angular` still has an old copy but `components` depends on the new variant from `ui-common`. - `safeProvider`, `SafeProvider` and `SafeInjectionToken`. `libs/angular`re-exports these to avoid needing to update all consumers. --- .github/CODEOWNERS | 1 + .github/whitelist-capital-letters.txt | 17 --- .github/workflows/lint.yml | 1 + .../popup/two-factor-auth-duo.component.ts | 4 - .../popup/two-factor-auth-email.component.ts | 4 - apps/browser/tsconfig.json | 15 +- .../src/auth/two-factor-auth.component.ts | 4 - apps/desktop/tsconfig.json | 9 +- .../integration-card.component.spec.ts | 2 +- .../integration-grid.component.spec.ts | 2 +- apps/web/tsconfig.json | 11 +- bitwarden_license/bit-common/tsconfig.json | 15 +- bitwarden_license/bit-web/tsconfig.json | 1 + libs/angular/src/platform/pipes/i18n.pipe.ts | 3 + .../src/platform/utils/safe-provider.ts | 138 +----------------- libs/angular/src/services/injection-tokens.ts | 15 +- .../src/services/jslib-services.module.ts | 2 +- libs/angular/tsconfig.json | 1 + libs/auth/tsconfig.json | 11 +- libs/common/tsconfig.json | 5 +- .../src/badge-list/badge-list.component.ts | 3 +- .../components/src/banner/banner.component.ts | 3 +- .../src/dialog/dialog/dialog.component.ts | 3 +- .../form-control/form-control.component.ts | 2 +- .../src/form-field/error-summary.component.ts | 2 +- .../src/form-field/form-field.component.ts | 3 +- .../multi-select/multi-select.component.ts | 2 +- .../src/navigation/nav-group.component.ts | 3 +- .../src/navigation/side-nav.component.ts | 3 +- .../src/radio-button/radio-group.component.ts | 3 +- .../components/src/search/search.component.ts | 2 +- libs/components/src/shared/shared.module.ts | 2 +- libs/components/tsconfig.json | 3 +- libs/importer/tsconfig.json | 1 + libs/key-management/tsconfig.json | 3 +- libs/shared/tsconfig.spec.json | 9 +- libs/tools/card/tsconfig.json | 3 +- .../vault-export-ui/tsconfig.json | 1 + libs/tools/generator/components/tsconfig.json | 3 +- libs/tools/send/send-ui/tsconfig.json | 3 +- libs/ui/README.md | 5 + libs/ui/common/package.json | 15 ++ libs/ui/common/src/di/index.ts | 2 + libs/ui/common/src/di/safe-injection-token.ts | 14 ++ libs/ui/common/src/di/safe-provider.ts | 138 ++++++++++++++++++ .../common/src/di}/safe-provider.type.spec.ts | 4 +- .../src/shared => ui/common/src}/i18n.pipe.ts | 8 +- libs/ui/common/src/index.ts | 2 + libs/ui/common/tsconfig.json | 10 ++ libs/vault/tsconfig.json | 1 + tsconfig.eslint.json | 17 ++- tsconfig.json | 13 +- 52 files changed, 300 insertions(+), 247 deletions(-) create mode 100644 libs/ui/README.md create mode 100644 libs/ui/common/package.json create mode 100644 libs/ui/common/src/di/index.ts create mode 100644 libs/ui/common/src/di/safe-injection-token.ts create mode 100644 libs/ui/common/src/di/safe-provider.ts rename libs/{angular/src/platform/utils => ui/common/src/di}/safe-provider.type.spec.ts (96%) rename libs/{components/src/shared => ui/common/src}/i18n.pipe.ts (77%) create mode 100644 libs/ui/common/src/index.ts create mode 100644 libs/ui/common/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cb36d87b9e..d7cca18960 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -111,6 +111,7 @@ apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-autofill-dev @bit ## Component Library ## .storybook @bitwarden/team-design-system libs/components @bitwarden/team-design-system +libs/ui @bitwarden/team-design-system apps/browser/src/platform/popup/layout @bitwarden/team-design-system apps/browser/src/popup/app-routing.animations.ts @bitwarden/team-design-system apps/web/src/app/layouts @bitwarden/team-design-system diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 73d323851e..653f6591c7 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -3,26 +3,12 @@ ./apps/browser/src/safari/desktop/Assets.xcassets/AppIcon.appiconset ./apps/browser/src/safari/desktop/Base.lproj ./apps/browser/store/windows/Assets -./bitwarden_license/README.md ./libs/angular/src/directives/cipherListVirtualScroll.directive.ts -./libs/admin-console/README.md -./libs/auth/README.md -./libs/billing/README.md -./libs/common/src/tools/integration/README.md -./libs/platform/README.md -./libs/key-management/README.md -./libs/tools/README.md -./libs/tools/export/vault-export/README.md -./libs/tools/send/README.md -./libs/tools/card/README.md -./libs/vault/README.md -./README.md ./LICENSE_BITWARDEN.txt ./CONTRIBUTING.md ./LICENSE_GPL.txt ./LICENSE.txt ./apps/web/Dockerfile -./apps/web/README.md ./apps/desktop/resources/installerSidebar.bmp ./apps/desktop/resources/appx/SplashScreen.png ./apps/desktop/resources/appx/BadgeLogo.png @@ -30,10 +16,7 @@ ./apps/desktop/resources/appx/StoreLogo.png ./apps/desktop/resources/appx/Wide310x150Logo.png ./apps/desktop/resources/appx/Square44x44Logo.png -./apps/desktop/README.md ./apps/cli/stores/chocolatey/tools/VERIFICATION.txt -./apps/cli/README.md -./apps/browser/README.md ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts ./apps/browser/src/models/browserComponentState.ts diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 867de3844e..5bc566202c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,6 +34,7 @@ jobs: ! -path "*/.DS_Store" \ ! -path "*/*locales/*" \ ! -path "./.github/*" \ + ! -path "*/README.md" \ ! -path "*/Cargo.toml" \ ! -path "*/Cargo.lock" \ ! -path "./apps/desktop/macos/*" \ diff --git a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts index 53aedc7a5f..687dc68392 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts @@ -27,9 +27,6 @@ import { FormFieldModule } from "../../../../../libs/components/src/form-field"; import { LinkModule } from "../../../../../libs/components/src/link"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports -import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; @@ -50,7 +47,6 @@ import { ZonedMessageListenerService } from "../../platform/browser/zoned-messag AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], }) export class TwoFactorAuthDuoComponent extends TwoFactorAuthDuoBaseComponent diff --git a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts index 723152adfa..7afe1eb889 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts @@ -23,9 +23,6 @@ import { FormFieldModule } from "../../../../../libs/components/src/form-field"; import { LinkModule } from "../../../../../libs/components/src/link"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports -import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; @@ -46,7 +43,6 @@ import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], }) export class TwoFactorAuthEmailComponent extends TwoFactorAuthEmailBaseComponent implements OnInit { private dialogService = inject(DialogService); diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index c1ef1443ac..2326e68f55 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -24,17 +24,18 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], + "@bitwarden/importer/core": ["../../libs/importer/src"], + "@bitwarden/importer/ui": ["../../libs/importer/src/components"], + "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], + "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], + "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/importer/core": ["../../libs/importer/src"], - "@bitwarden/importer/ui": ["../../libs/importer/src/components"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/vault": ["../../libs/vault/src"] }, "plugins": [ diff --git a/apps/desktop/src/auth/two-factor-auth.component.ts b/apps/desktop/src/auth/two-factor-auth.component.ts index 9e0898c39e..d8b80c28df 100644 --- a/apps/desktop/src/auth/two-factor-auth.component.ts +++ b/apps/desktop/src/auth/two-factor-auth.component.ts @@ -42,9 +42,6 @@ import { FormFieldModule } from "../../../../libs/components/src/form-field"; import { LinkModule } from "../../../../libs/components/src/link"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports -import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../libs/components/src/typography"; import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; @@ -73,6 +70,5 @@ import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; TwoFactorAuthDuoComponent, TwoFactorAuthWebAuthnComponent, ], - providers: [I18nPipe], }) export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent {} diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index da61ef22dd..cbeb1034d6 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -22,10 +22,6 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], @@ -34,6 +30,11 @@ "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], + "@bitwarden/vault-export-core": [ + "../../libs/tools/export/vault-export/vault-export-core/src" + ], + "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"] }, "plugins": [ diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts index c8b6a29042..b9d5474476 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts @@ -7,7 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { SharedModule } from "@bitwarden/components/src/shared"; -import { I18nPipe } from "@bitwarden/components/src/shared/i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; import { IntegrationCardComponent } from "./integration-card.component"; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts index c77ec455e0..ee124a41c0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts @@ -9,7 +9,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { ThemeTypes } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { SharedModule } from "@bitwarden/components/src/shared"; -import { I18nPipe } from "@bitwarden/components/src/shared/i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; import { IntegrationCardComponent } from "../integration-card/integration-card.component"; import { Integration } from "../models"; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 678db7c4af..701808df13 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -7,8 +7,8 @@ "paths": { "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], + "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/bit-common/*": ["../../bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], @@ -18,10 +18,6 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], @@ -29,6 +25,11 @@ "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], + "@bitwarden/vault-export-core": [ + "../../libs/tools/export/vault-export/vault-export-core/src" + ], + "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/web-vault/*": ["src/*"] } diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index 7791840950..ec1d3787f8 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -9,6 +9,7 @@ "@bitwarden/angular/*": ["../../libs/angular/src/*"], "@bitwarden/auth": ["../../libs/auth/src"], "@bitwarden/billing": ["../../libs/billing/src"], + "@bitwarden/bit-common/*": ["../bit-common/src/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], @@ -16,18 +17,18 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], + "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], + "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], - "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/vault": ["../../libs/vault/src"], - "@bitwarden/web-vault/*": ["../../apps/web/src/*"], - "@bitwarden/bit-common/*": ["../bit-common/src/*"] + "@bitwarden/web-vault/*": ["../../apps/web/src/*"] } } } diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index c4304ec2bd..13a6466b3b 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -26,6 +26,7 @@ "@bitwarden/key-management": ["../../libs/key-management/src"], "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/vault": ["../../libs/vault/src"], diff --git a/libs/angular/src/platform/pipes/i18n.pipe.ts b/libs/angular/src/platform/pipes/i18n.pipe.ts index 1f92bbb19a..a6fdbc7825 100644 --- a/libs/angular/src/platform/pipes/i18n.pipe.ts +++ b/libs/angular/src/platform/pipes/i18n.pipe.ts @@ -2,6 +2,9 @@ import { Pipe, PipeTransform } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +/** + * @deprecated: Please use the I18nPipe from @bitwarden/ui-common + */ @Pipe({ name: "i18n", }) diff --git a/libs/angular/src/platform/utils/safe-provider.ts b/libs/angular/src/platform/utils/safe-provider.ts index e7547f9b82..82b5affcc2 100644 --- a/libs/angular/src/platform/utils/safe-provider.ts +++ b/libs/angular/src/platform/utils/safe-provider.ts @@ -1,138 +1,4 @@ -import { Provider } from "@angular/core"; -import { Constructor, Opaque } from "type-fest"; - -import { SafeInjectionToken } from "../../services/injection-tokens"; - /** - * The return type of the {@link safeProvider} helper function. - * Used to distinguish a type safe provider definition from a non-type safe provider definition. + * @deprecated: Please use the SafeProvider & safeProvider from @bitwarden/ui-common */ -export type SafeProvider = Opaque; - -// TODO: type-fest also provides a type like this when we upgrade >= 3.7.0 -type AbstractConstructor = abstract new (...args: any) => T; - -type MapParametersToDeps = { - [K in keyof T]: AbstractConstructor | SafeInjectionToken; -}; - -type SafeInjectionTokenType = T extends SafeInjectionToken ? J : never; - -/** - * Gets the instance type from a constructor, abstract constructor, or SafeInjectionToken - */ -type ProviderInstanceType = - T extends SafeInjectionToken - ? InstanceType> - : T extends Constructor | AbstractConstructor - ? InstanceType - : never; - -/** - * Represents a dependency provided with the useClass option. - */ -type SafeClassProvider< - A extends AbstractConstructor | SafeInjectionToken, - I extends Constructor>, - D extends MapParametersToDeps>, -> = { - provide: A; - useClass: I; - deps: D; -}; - -/** - * Represents a dependency provided with the useValue option. - */ -type SafeValueProvider, V extends SafeInjectionTokenType> = { - provide: A; - useValue: V; -}; - -/** - * Represents a dependency provided with the useFactory option. - */ -type SafeFactoryProvider< - A extends AbstractConstructor | SafeInjectionToken, - I extends (...args: any) => ProviderInstanceType, - D extends MapParametersToDeps>, -> = { - provide: A; - useFactory: I; - deps: D; - multi?: boolean; -}; - -/** - * Represents a dependency provided with the useExisting option. - */ -type SafeExistingProvider< - A extends Constructor | AbstractConstructor | SafeInjectionToken, - I extends Constructor> | AbstractConstructor>, -> = { - provide: A; - useExisting: I; -}; - -/** - * Represents a dependency where there is no abstract token, the token is the implementation - */ -type SafeConcreteProvider< - I extends Constructor, - D extends MapParametersToDeps>, -> = { - provide: I; - deps: D; -}; - -/** - * If useAngularDecorators: true is specified, do not require a deps array. - * This is a manual override for where @Injectable decorators are used - */ -type UseAngularDecorators = Omit & { - useAngularDecorators: true; -}; - -/** - * Represents a type with a deps array that may optionally be overridden with useAngularDecorators - */ -type AllowAngularDecorators = T | UseAngularDecorators; - -/** - * A factory function that creates a provider for the ngModule providers array. - * This (almost) guarantees type safety for your provider definition. It does nothing at runtime. - * Warning: the useAngularDecorators option provides an override where your class uses the Injectable decorator, - * however this cannot be enforced by the type system and will not cause an error if the decorator is not used. - * @example safeProvider({ provide: MyService, useClass: DefaultMyService, deps: [AnotherService] }) - * @param provider Your provider object in the usual shape (e.g. using useClass, useValue, useFactory, etc.) - * @returns The exact same object without modification (pass-through). - */ -export const safeProvider = < - // types for useClass - AClass extends AbstractConstructor | SafeInjectionToken, - IClass extends Constructor>, - DClass extends MapParametersToDeps>, - // types for useValue - AValue extends SafeInjectionToken, - VValue extends SafeInjectionTokenType, - // types for useFactory - AFactory extends AbstractConstructor | SafeInjectionToken, - IFactory extends (...args: any) => ProviderInstanceType, - DFactory extends MapParametersToDeps>, - // types for useExisting - AExisting extends Constructor | AbstractConstructor | SafeInjectionToken, - IExisting extends - | Constructor> - | AbstractConstructor>, - // types for no token - IConcrete extends Constructor, - DConcrete extends MapParametersToDeps>, ->( - provider: - | AllowAngularDecorators> - | SafeValueProvider - | AllowAngularDecorators> - | SafeExistingProvider - | AllowAngularDecorators> - | Constructor, -): SafeProvider => provider as SafeProvider; +export { SafeProvider, safeProvider } from "@bitwarden/ui-common"; diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 3842c3250e..2c740d5bb4 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,6 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { InjectionToken } from "@angular/core"; import { Observable, Subject } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; @@ -14,17 +13,9 @@ import { Theme } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Message } from "@bitwarden/common/platform/messaging"; import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; - -declare const tag: unique symbol; -/** - * A (more) typesafe version of InjectionToken which will more strictly enforce the generic type parameter. - * @remarks The default angular implementation does not use the generic type to define the structure of the object, - * so the structural type system will not complain about a mismatch in the type parameter. - * This is solved by assigning T to an arbitrary private property. - */ -export class SafeInjectionToken extends InjectionToken { - private readonly [tag]: T; -} +import { SafeInjectionToken } from "@bitwarden/ui-common"; +// Re-export the SafeInjectionToken from ui-common +export { SafeInjectionToken } from "@bitwarden/ui-common"; export const WINDOW = new SafeInjectionToken("WINDOW"); export const OBSERVABLE_MEMORY_STORAGE = new SafeInjectionToken< diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 803808612c..c1d25407c0 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -293,6 +293,7 @@ import { UserAsymmetricKeysRegenerationApiService, DefaultUserAsymmetricKeysRegenerationApiService, } from "@bitwarden/key-management"; +import { SafeInjectionToken } from "@bitwarden/ui-common"; import { PasswordRepromptService } from "@bitwarden/vault"; import { VaultExportService, @@ -323,7 +324,6 @@ import { MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE, OBSERVABLE_MEMORY_STORAGE, - SafeInjectionToken, SECURE_STORAGE, STATE_FACTORY, SUPPORTS_SECURE_STORAGE, diff --git a/libs/angular/tsconfig.json b/libs/angular/tsconfig.json index 6c510f8149..b638410a6a 100644 --- a/libs/angular/tsconfig.json +++ b/libs/angular/tsconfig.json @@ -16,6 +16,7 @@ "@bitwarden/importer/core": ["../importer/src"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"], "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], "@bitwarden/vault": ["../vault/src"] } diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json index 9be942d38d..8d08522ffc 100644 --- a/libs/auth/tsconfig.json +++ b/libs/auth/tsconfig.json @@ -4,17 +4,18 @@ "resolveJsonModule": true, "paths": { "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/auth/angular": ["../auth/src/angular"], "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/components": ["../components/src"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"], "@bitwarden/generator-core": ["../tools/generator/core/src"], "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"] + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"] } }, "include": ["src", "spec"], diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index c28b60e28f..2d1379f9c5 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -6,9 +6,12 @@ "@bitwarden/auth/common": ["../auth/src/common"], // TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved "@bitwarden/common/*": ["../common/src/*"], + // TODO: Remove once billing stops depending on components "@bitwarden/components": ["../components/src"], "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"] + "@bitwarden/platform": ["../platform/src"], + // TODO: Remove once billing stops depending on components + "@bitwarden/ui-common": ["../ui/common/src"] } }, "include": ["src", "spec", "./custom-matchers.d.ts", "../key-management/src/index.ts"], diff --git a/libs/components/src/badge-list/badge-list.component.ts b/libs/components/src/badge-list/badge-list.component.ts index ac8cb3281a..7d152761ed 100644 --- a/libs/components/src/badge-list/badge-list.component.ts +++ b/libs/components/src/badge-list/badge-list.component.ts @@ -3,8 +3,9 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BadgeModule, BadgeVariant } from "../badge"; -import { I18nPipe } from "../shared/i18n.pipe"; @Component({ selector: "bit-badge-list", diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index d3f6432997..a7b710d6a7 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -3,8 +3,9 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { IconButtonModule } from "../icon-button"; -import { I18nPipe } from "../shared/i18n.pipe"; type BannerTypes = "premium" | "info" | "warning" | "danger"; diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index ed47201805..e9e3e89825 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -4,8 +4,9 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { CommonModule } from "@angular/common"; import { Component, HostBinding, Input } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitIconButtonComponent } from "../../icon-button/icon-button.component"; -import { I18nPipe } from "../../shared/i18n.pipe"; import { TypographyDirective } from "../../typography/typography.directive"; import { fadeIn } from "../animations"; import { DialogCloseDirective } from "../directives/dialog-close.directive"; diff --git a/libs/components/src/form-control/form-control.component.ts b/libs/components/src/form-control/form-control.component.ts index 9b87c44157..d22d49ac03 100644 --- a/libs/components/src/form-control/form-control.component.ts +++ b/libs/components/src/form-control/form-control.component.ts @@ -5,8 +5,8 @@ import { NgClass, NgIf } from "@angular/common"; import { Component, ContentChild, HostBinding, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nPipe } from "@bitwarden/ui-common"; -import { I18nPipe } from "../shared/i18n.pipe"; import { TypographyDirective } from "../typography/typography.directive"; import { BitFormControlAbstraction } from "./form-control.abstraction"; diff --git a/libs/components/src/form-field/error-summary.component.ts b/libs/components/src/form-field/error-summary.component.ts index beed32a88a..f9325d8f82 100644 --- a/libs/components/src/form-field/error-summary.component.ts +++ b/libs/components/src/form-field/error-summary.component.ts @@ -4,7 +4,7 @@ import { NgIf } from "@angular/common"; import { Component, Input } from "@angular/core"; import { AbstractControl, UntypedFormGroup } from "@angular/forms"; -import { I18nPipe } from "../shared/i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "bit-error-summary", diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 9f41c6cf6a..4f7c2a6748 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -14,10 +14,11 @@ import { signal, } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitHintComponent } from "../form-control/hint.component"; import { BitLabel } from "../form-control/label.component"; import { inputBorderClasses } from "../input/input.directive"; -import { I18nPipe } from "../shared/i18n.pipe"; import { BitErrorComponent } from "./error.component"; import { BitFormFieldControl } from "./form-field-control"; diff --git a/libs/components/src/multi-select/multi-select.component.ts b/libs/components/src/multi-select/multi-select.component.ts index 53e51bfe2f..71b00404cf 100644 --- a/libs/components/src/multi-select/multi-select.component.ts +++ b/libs/components/src/multi-select/multi-select.component.ts @@ -24,10 +24,10 @@ import { import { NgSelectComponent, NgSelectModule } from "@ng-select/ng-select"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nPipe } from "@bitwarden/ui-common"; import { BadgeModule } from "../badge"; import { BitFormFieldControl } from "../form-field/form-field-control"; -import { I18nPipe } from "../shared/i18n.pipe"; import { SelectItemView } from "./models/select-item-view"; diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index d615bfe058..37244f37c8 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -12,8 +12,9 @@ import { SkipSelf, } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { IconButtonModule } from "../icon-button"; -import { I18nPipe } from "../shared/i18n.pipe"; import { NavBaseComponent } from "./nav-base.component"; import { NavGroupAbstraction, NavItemComponent } from "./nav-item.component"; diff --git a/libs/components/src/navigation/side-nav.component.ts b/libs/components/src/navigation/side-nav.component.ts index c86a517100..e8e4f131d6 100644 --- a/libs/components/src/navigation/side-nav.component.ts +++ b/libs/components/src/navigation/side-nav.component.ts @@ -4,8 +4,9 @@ import { CdkTrapFocus } from "@angular/cdk/a11y"; import { CommonModule } from "@angular/common"; import { Component, ElementRef, Input, ViewChild } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitIconButtonComponent } from "../icon-button/icon-button.component"; -import { I18nPipe } from "../shared/i18n.pipe"; import { NavDividerComponent } from "./nav-divider.component"; import { SideNavService } from "./side-nav.service"; diff --git a/libs/components/src/radio-button/radio-group.component.ts b/libs/components/src/radio-button/radio-group.component.ts index b9e48f4644..4ab626f796 100644 --- a/libs/components/src/radio-button/radio-group.component.ts +++ b/libs/components/src/radio-button/radio-group.component.ts @@ -4,8 +4,9 @@ import { NgIf, NgTemplateOutlet } from "@angular/common"; import { Component, ContentChild, HostBinding, Input, Optional, Self } from "@angular/core"; import { ControlValueAccessor, NgControl, Validators } from "@angular/forms"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitLabel } from "../form-control/label.component"; -import { I18nPipe } from "../shared/i18n.pipe"; let nextId = 0; diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index 9a811ce677..7f1bd781e9 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -9,10 +9,10 @@ import { } from "@angular/forms"; import { isBrowserSafariApi } from "@bitwarden/platform"; +import { I18nPipe } from "@bitwarden/ui-common"; import { InputModule } from "../input/input.module"; import { FocusableElement } from "../shared/focusable-element"; -import { I18nPipe } from "../shared/i18n.pipe"; let nextId = 0; diff --git a/libs/components/src/shared/shared.module.ts b/libs/components/src/shared/shared.module.ts index 253b049f8f..99d052c335 100644 --- a/libs/components/src/shared/shared.module.ts +++ b/libs/components/src/shared/shared.module.ts @@ -1,7 +1,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { I18nPipe } from "./i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; @NgModule({ imports: [CommonModule, I18nPipe], diff --git a/libs/components/tsconfig.json b/libs/components/tsconfig.json index 71eef15fac..abd5830d42 100644 --- a/libs/components/tsconfig.json +++ b/libs/components/tsconfig.json @@ -20,7 +20,8 @@ "lib": ["es2020", "dom"], "paths": { "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/platform": ["../platform/src"] + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"] }, "plugins": [ { diff --git a/libs/importer/tsconfig.json b/libs/importer/tsconfig.json index 2235cccb5c..09eb33e288 100644 --- a/libs/importer/tsconfig.json +++ b/libs/importer/tsconfig.json @@ -13,6 +13,7 @@ "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"], "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"] } }, diff --git a/libs/key-management/tsconfig.json b/libs/key-management/tsconfig.json index 8279f14c78..e1e618314e 100644 --- a/libs/key-management/tsconfig.json +++ b/libs/key-management/tsconfig.json @@ -13,7 +13,8 @@ "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/platform": ["../platform/src"] + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"] } }, "include": ["src", "spec"], diff --git a/libs/shared/tsconfig.spec.json b/libs/shared/tsconfig.spec.json index 2366507918..92c957c297 100644 --- a/libs/shared/tsconfig.spec.json +++ b/libs/shared/tsconfig.spec.json @@ -5,8 +5,8 @@ "paths": { "@bitwarden/admin-console/common": ["../admin-console/src/common"], "@bitwarden/angular/*": ["../angular/src/*"], - "@bitwarden/auth/common": ["../auth/src/common"], "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], "@bitwarden/billing": ["../billing/src"], "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/components": ["../components/src"], @@ -15,16 +15,17 @@ "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault-export-ui": ["../tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["../importer/src"], "@bitwarden/importer/ui": ["../importer/src/components"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/key-management/angular": ["../key-management/src/angular"], + "@bitwarden/node/*": ["../node/src/*"], "@bitwarden/platform": ["../platform/src"], "@bitwarden/send-ui": ["../tools/send/send-ui/src"], "@bitwarden/tools-card": ["../tools/card/src"], - "@bitwarden/node/*": ["../node/src/*"], + "@bitwarden/ui-common": ["../ui/common/src"], + "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": ["../tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["../vault/src"] } } diff --git a/libs/tools/card/tsconfig.json b/libs/tools/card/tsconfig.json index 9c910521cc..050a1748b7 100644 --- a/libs/tools/card/tsconfig.json +++ b/libs/tools/card/tsconfig.json @@ -8,7 +8,8 @@ "@bitwarden/common/*": ["../../common/src/*"], "@bitwarden/components": ["../../components/src"], "@bitwarden/key-management": ["../../key-management/src"], - "@bitwarden/platform": ["../../platform/src"] + "@bitwarden/platform": ["../../platform/src"], + "@bitwarden/ui-common": ["../../ui/common/src"] } }, "include": ["src"], diff --git a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json index 8c8a04d4b6..1732817986 100644 --- a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json +++ b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json @@ -14,6 +14,7 @@ "@bitwarden/generator-navigation": ["../../../../tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../../../../key-management/src"], "@bitwarden/platform": ["../../../../platform/src"], + "@bitwarden/ui-common": ["../../../../ui/common/src"], "@bitwarden/vault-export-core": [ "../../../../tools/export/vault-export/vault-export-core/src" ] diff --git a/libs/tools/generator/components/tsconfig.json b/libs/tools/generator/components/tsconfig.json index 76b060334f..e0e4da268d 100644 --- a/libs/tools/generator/components/tsconfig.json +++ b/libs/tools/generator/components/tsconfig.json @@ -10,7 +10,8 @@ "@bitwarden/generator-core": ["../../../tools/generator/core/src"], "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], "@bitwarden/key-management": ["../../../key-management/src"], - "@bitwarden/platform": ["../../../platform/src"] + "@bitwarden/platform": ["../../../platform/src"], + "@bitwarden/ui-common": ["../../../ui/common/src"] } }, "include": ["src"], diff --git a/libs/tools/send/send-ui/tsconfig.json b/libs/tools/send/send-ui/tsconfig.json index 671154e0a0..e6d6680ad4 100644 --- a/libs/tools/send/send-ui/tsconfig.json +++ b/libs/tools/send/send-ui/tsconfig.json @@ -13,7 +13,8 @@ "@bitwarden/generator-legacy": ["../../../tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../../tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../../../key-management/src"], - "@bitwarden/platform": ["../../../platform/src"] + "@bitwarden/platform": ["../../../platform/src"], + "@bitwarden/ui-common": ["../../../ui/common/src"] } }, "include": ["src"], diff --git a/libs/ui/README.md b/libs/ui/README.md new file mode 100644 index 0000000000..e245aac71c --- /dev/null +++ b/libs/ui/README.md @@ -0,0 +1,5 @@ +# UI Foundation + +Core UI libraries maintained by the UI Foundation team. + +- _ui-common_: Low-level utilities for Angular applications. diff --git a/libs/ui/common/package.json b/libs/ui/common/package.json new file mode 100644 index 0000000000..f1b03a3ebd --- /dev/null +++ b/libs/ui/common/package.json @@ -0,0 +1,15 @@ +{ + "name": "@bitwarden/ui-common", + "version": "0.0.0", + "description": "Low-level utilities for Angular applications", + "keywords": [ + "bitwarden" + ], + "author": "Bitwarden Inc.", + "homepage": "https://bitwarden.com", + "repository": { + "type": "git", + "url": "https://github.com/bitwarden/clients" + }, + "license": "GPL-3.0" +} diff --git a/libs/ui/common/src/di/index.ts b/libs/ui/common/src/di/index.ts new file mode 100644 index 0000000000..7b0705f136 --- /dev/null +++ b/libs/ui/common/src/di/index.ts @@ -0,0 +1,2 @@ +export * from "./safe-injection-token"; +export * from "./safe-provider"; diff --git a/libs/ui/common/src/di/safe-injection-token.ts b/libs/ui/common/src/di/safe-injection-token.ts new file mode 100644 index 0000000000..aad081e1c7 --- /dev/null +++ b/libs/ui/common/src/di/safe-injection-token.ts @@ -0,0 +1,14 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { InjectionToken } from "@angular/core"; + +declare const tag: unique symbol; +/** + * A (more) typesafe version of InjectionToken which will more strictly enforce the generic type parameter. + * @remarks The default angular implementation does not use the generic type to define the structure of the object, + * so the structural type system will not complain about a mismatch in the type parameter. + * This is solved by assigning T to an arbitrary private property. + */ +export class SafeInjectionToken extends InjectionToken { + private readonly [tag]: T; +} diff --git a/libs/ui/common/src/di/safe-provider.ts b/libs/ui/common/src/di/safe-provider.ts new file mode 100644 index 0000000000..002aa69a50 --- /dev/null +++ b/libs/ui/common/src/di/safe-provider.ts @@ -0,0 +1,138 @@ +import { Provider } from "@angular/core"; +import { Constructor, Opaque } from "type-fest"; + +import { SafeInjectionToken } from "./safe-injection-token"; + +/** + * The return type of the {@link safeProvider} helper function. + * Used to distinguish a type safe provider definition from a non-type safe provider definition. + */ +export type SafeProvider = Opaque; + +// TODO: type-fest also provides a type like this when we upgrade >= 3.7.0 +type AbstractConstructor = abstract new (...args: any) => T; + +type MapParametersToDeps = { + [K in keyof T]: AbstractConstructor | SafeInjectionToken; +}; + +type SafeInjectionTokenType = T extends SafeInjectionToken ? J : never; + +/** + * Gets the instance type from a constructor, abstract constructor, or SafeInjectionToken + */ +type ProviderInstanceType = + T extends SafeInjectionToken + ? InstanceType> + : T extends Constructor | AbstractConstructor + ? InstanceType + : never; + +/** + * Represents a dependency provided with the useClass option. + */ +type SafeClassProvider< + A extends AbstractConstructor | SafeInjectionToken, + I extends Constructor>, + D extends MapParametersToDeps>, +> = { + provide: A; + useClass: I; + deps: D; +}; + +/** + * Represents a dependency provided with the useValue option. + */ +type SafeValueProvider, V extends SafeInjectionTokenType> = { + provide: A; + useValue: V; +}; + +/** + * Represents a dependency provided with the useFactory option. + */ +type SafeFactoryProvider< + A extends AbstractConstructor | SafeInjectionToken, + I extends (...args: any) => ProviderInstanceType, + D extends MapParametersToDeps>, +> = { + provide: A; + useFactory: I; + deps: D; + multi?: boolean; +}; + +/** + * Represents a dependency provided with the useExisting option. + */ +type SafeExistingProvider< + A extends Constructor | AbstractConstructor | SafeInjectionToken, + I extends Constructor> | AbstractConstructor>, +> = { + provide: A; + useExisting: I; +}; + +/** + * Represents a dependency where there is no abstract token, the token is the implementation + */ +type SafeConcreteProvider< + I extends Constructor, + D extends MapParametersToDeps>, +> = { + provide: I; + deps: D; +}; + +/** + * If useAngularDecorators: true is specified, do not require a deps array. + * This is a manual override for where @Injectable decorators are used + */ +type UseAngularDecorators = Omit & { + useAngularDecorators: true; +}; + +/** + * Represents a type with a deps array that may optionally be overridden with useAngularDecorators + */ +type AllowAngularDecorators = T | UseAngularDecorators; + +/** + * A factory function that creates a provider for the ngModule providers array. + * This (almost) guarantees type safety for your provider definition. It does nothing at runtime. + * Warning: the useAngularDecorators option provides an override where your class uses the Injectable decorator, + * however this cannot be enforced by the type system and will not cause an error if the decorator is not used. + * @example safeProvider({ provide: MyService, useClass: DefaultMyService, deps: [AnotherService] }) + * @param provider Your provider object in the usual shape (e.g. using useClass, useValue, useFactory, etc.) + * @returns The exact same object without modification (pass-through). + */ +export const safeProvider = < + // types for useClass + AClass extends AbstractConstructor | SafeInjectionToken, + IClass extends Constructor>, + DClass extends MapParametersToDeps>, + // types for useValue + AValue extends SafeInjectionToken, + VValue extends SafeInjectionTokenType, + // types for useFactory + AFactory extends AbstractConstructor | SafeInjectionToken, + IFactory extends (...args: any) => ProviderInstanceType, + DFactory extends MapParametersToDeps>, + // types for useExisting + AExisting extends Constructor | AbstractConstructor | SafeInjectionToken, + IExisting extends + | Constructor> + | AbstractConstructor>, + // types for no token + IConcrete extends Constructor, + DConcrete extends MapParametersToDeps>, +>( + provider: + | AllowAngularDecorators> + | SafeValueProvider + | AllowAngularDecorators> + | SafeExistingProvider + | AllowAngularDecorators> + | Constructor, +): SafeProvider => provider as SafeProvider; diff --git a/libs/angular/src/platform/utils/safe-provider.type.spec.ts b/libs/ui/common/src/di/safe-provider.type.spec.ts similarity index 96% rename from libs/angular/src/platform/utils/safe-provider.type.spec.ts rename to libs/ui/common/src/di/safe-provider.type.spec.ts index 6fe6d0d0b6..afc7071af1 100644 --- a/libs/angular/src/platform/utils/safe-provider.type.spec.ts +++ b/libs/ui/common/src/di/safe-provider.type.spec.ts @@ -11,7 +11,7 @@ class FooFactory { } abstract class FooService { - createFoo: (str: string) => string; + abstract createFoo(str: string): string; } class DefaultFooService implements FooService { @@ -29,7 +29,7 @@ class BarFactory { } abstract class BarService { - createBar: (num: number) => number; + abstract createBar(num: number): number; } class DefaultBarService implements BarService { diff --git a/libs/components/src/shared/i18n.pipe.ts b/libs/ui/common/src/i18n.pipe.ts similarity index 77% rename from libs/components/src/shared/i18n.pipe.ts rename to libs/ui/common/src/i18n.pipe.ts index 91bf0b3198..fdcfec0cea 100644 --- a/libs/components/src/shared/i18n.pipe.ts +++ b/libs/ui/common/src/i18n.pipe.ts @@ -3,7 +3,13 @@ import { Pipe, PipeTransform } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; /** - * Temporarily duplicate this pipe + * Localizes the specified string. + * + * @example + * {{ 'key' | i18n }} + * + * @example + * {{ 'key' | i18n: 'param1' }} */ @Pipe({ name: "i18n", diff --git a/libs/ui/common/src/index.ts b/libs/ui/common/src/index.ts new file mode 100644 index 0000000000..97e5510811 --- /dev/null +++ b/libs/ui/common/src/index.ts @@ -0,0 +1,2 @@ +export * from "./di"; +export * from "./i18n.pipe"; diff --git a/libs/ui/common/tsconfig.json b/libs/ui/common/tsconfig.json new file mode 100644 index 0000000000..31062d41a1 --- /dev/null +++ b/libs/ui/common/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/common/*": ["../../common/src/*"] + } + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/libs/vault/tsconfig.json b/libs/vault/tsconfig.json index 8318212e81..e1515183f2 100644 --- a/libs/vault/tsconfig.json +++ b/libs/vault/tsconfig.json @@ -15,6 +15,7 @@ "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"], "@bitwarden/vault": ["../vault/src"] } }, diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index a69452389f..980d7832ac 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -20,6 +20,7 @@ "@bitwarden/angular/*": ["./libs/angular/src/*"], "@bitwarden/auth": ["./libs/auth/src"], "@bitwarden/billing": ["./libs/billing/src"], + "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], @@ -27,18 +28,18 @@ "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["./libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["./libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [".libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault-export-ui": [".libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["./libs/importer/src"], "@bitwarden/importer/ui": ["./libs/importer/src/components"], - "@bitwarden/send-ui": [".libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": [".libs/tools/card/src"], - "@bitwarden/platform": ["./libs/platform/src"], - "@bitwarden/node/*": ["./libs/node/src/*"], - "@bitwarden/vault": ["./libs/vault/src"], "@bitwarden/key-management": ["./libs/key-management/src"], "@bitwarden/key-management/angular": ["./libs/key-management/src/angular"], - "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] + "@bitwarden/node/*": ["./libs/node/src/*"], + "@bitwarden/platform": ["./libs/platform/src"], + "@bitwarden/send-ui": [".libs/tools/send/send-ui/src"], + "@bitwarden/tools-card": [".libs/tools/card/src"], + "@bitwarden/ui-common": ["./libs/ui/common/src"], + "@bitwarden/vault-export-core": [".libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": [".libs/tools/export/vault-export/vault-export-ui/src"], + "@bitwarden/vault": ["./libs/vault/src"] }, "plugins": [ { diff --git a/tsconfig.json b/tsconfig.json index 91b4ee7dd6..efa2ff70e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,9 +18,10 @@ "paths": { "@bitwarden/admin-console/common": ["./libs/admin-console/src/common"], "@bitwarden/angular/*": ["./libs/angular/src/*"], - "@bitwarden/auth/common": ["./libs/auth/src/common"], "@bitwarden/auth/angular": ["./libs/auth/src/angular"], + "@bitwarden/auth/common": ["./libs/auth/src/common"], "@bitwarden/billing": ["./libs/billing/src"], + "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], @@ -28,19 +29,19 @@ "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["./libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["./libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault-export-ui": ["./libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["./libs/importer/src"], "@bitwarden/importer/ui": ["./libs/importer/src/components"], "@bitwarden/key-management": ["./libs/key-management/src"], "@bitwarden/key-management/angular": ["./libs/key-management/src/angular"], + "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["./libs/tools/card/src"], - "@bitwarden/node/*": ["./libs/node/src/*"], - "@bitwarden/web-vault/*": ["./apps/web/src/*"], + "@bitwarden/ui-common": ["./libs/ui/common/src"], + "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": ["./libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["./libs/vault/src"], - "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] + "@bitwarden/web-vault/*": ["./apps/web/src/*"] }, "plugins": [ {