@vendure/admin-ui 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/bundles/vendure-admin-ui-catalog.umd.js +206 -172
  2. package/bundles/vendure-admin-ui-catalog.umd.js.map +1 -1
  3. package/bundles/vendure-admin-ui-core.umd.js +2333 -1860
  4. package/bundles/vendure-admin-ui-core.umd.js.map +1 -1
  5. package/bundles/vendure-admin-ui-dashboard.umd.js +2 -2
  6. package/bundles/vendure-admin-ui-login.umd.js +2 -2
  7. package/bundles/vendure-admin-ui-login.umd.js.map +1 -1
  8. package/bundles/vendure-admin-ui-marketing.umd.js +1 -1
  9. package/bundles/vendure-admin-ui-marketing.umd.js.map +1 -1
  10. package/bundles/vendure-admin-ui-order.umd.js +1 -1
  11. package/bundles/vendure-admin-ui-order.umd.js.map +1 -1
  12. package/catalog/components/collection-contents/collection-contents.component.d.ts +7 -2
  13. package/catalog/components/collection-detail/collection-detail.component.d.ts +12 -4
  14. package/catalog/components/collection-list/collection-list.component.d.ts +2 -0
  15. package/catalog/components/collection-tree/array-to-tree.d.ts +1 -1
  16. package/catalog/components/collection-tree/collection-tree-node.component.d.ts +5 -1
  17. package/catalog/components/collection-tree/collection-tree.component.d.ts +1 -0
  18. package/catalog/providers/product-detail/product-detail.service.d.ts +2 -2
  19. package/catalog/public_api.d.ts +0 -1
  20. package/catalog/vendure-admin-ui-catalog.metadata.json +1 -1
  21. package/core/common/generated-types.d.ts +32 -3
  22. package/core/common/utilities/selection-manager.d.ts +23 -0
  23. package/core/common/version.d.ts +1 -1
  24. package/core/components/app-shell/app-shell.component.d.ts +1 -0
  25. package/core/data/definitions/collection-definitions.d.ts +1 -0
  26. package/core/data/providers/collection-data.service.d.ts +6 -2
  27. package/core/providers/local-storage/local-storage.service.d.ts +1 -0
  28. package/core/public_api.d.ts +5 -0
  29. package/core/shared/components/asset-gallery/asset-gallery.component.d.ts +21 -6
  30. package/core/shared/components/configurable-input/configurable-input.component.d.ts +7 -2
  31. package/core/shared/components/product-multi-selector-dialog/product-multi-selector-dialog.component.d.ts +35 -0
  32. package/{catalog → core/shared}/components/product-search-input/product-search-input.component.d.ts +1 -1
  33. package/core/shared/components/select-toggle/select-toggle.component.d.ts +1 -0
  34. package/core/shared/dynamic-form-inputs/combination-mode-form-input/combination-mode-form-input.component.d.ts +25 -0
  35. package/core/shared/dynamic-form-inputs/product-multi-selector-form-input/product-multi-selector-form-input.component.d.ts +20 -0
  36. package/core/shared/dynamic-form-inputs/register-dynamic-input-components.d.ts +3 -1
  37. package/core/shared/dynamic-form-inputs/relation-form-input/asset/relation-asset-input.component.d.ts +5 -2
  38. package/core/vendure-admin-ui-core.metadata.json +1 -1
  39. package/dashboard/vendure-admin-ui-dashboard.metadata.json +1 -1
  40. package/esm2015/catalog/catalog.module.js +1 -3
  41. package/esm2015/catalog/components/assets/assets.component.js +1 -1
  42. package/esm2015/catalog/components/collection-contents/collection-contents.component.js +51 -14
  43. package/esm2015/catalog/components/collection-detail/collection-detail.component.js +67 -29
  44. package/esm2015/catalog/components/collection-list/collection-list.component.js +30 -4
  45. package/esm2015/catalog/components/collection-tree/array-to-tree.js +3 -3
  46. package/esm2015/catalog/components/collection-tree/collection-tree-node.component.js +27 -4
  47. package/esm2015/catalog/components/collection-tree/collection-tree.component.js +4 -2
  48. package/esm2015/catalog/components/product-detail/product-detail.component.js +1 -1
  49. package/esm2015/catalog/components/product-list/product-list.component.js +3 -3
  50. package/esm2015/catalog/components/product-variants-list/product-variants-list.component.js +1 -1
  51. package/esm2015/catalog/public_api.js +1 -2
  52. package/esm2015/core/app.component.module.js +1 -1
  53. package/esm2015/core/common/base-detail.component.js +1 -1
  54. package/esm2015/core/common/deactivate-aware.js +1 -1
  55. package/esm2015/core/common/generated-types.js +1 -1
  56. package/esm2015/core/common/introspection-result.js +255 -189
  57. package/esm2015/core/common/utilities/selection-manager.js +64 -0
  58. package/esm2015/core/common/version.js +2 -2
  59. package/esm2015/core/components/app-shell/app-shell.component.js +4 -3
  60. package/esm2015/core/core.module.js +1 -1
  61. package/esm2015/core/data/definitions/collection-definitions.js +18 -1
  62. package/esm2015/core/data/definitions/order-definitions.js +2 -1
  63. package/esm2015/core/data/definitions/shared-definitions.js +29 -28
  64. package/esm2015/core/data/providers/collection-data.service.js +5 -2
  65. package/esm2015/core/providers/local-storage/local-storage.service.js +1 -1
  66. package/esm2015/core/public_api.js +6 -1
  67. package/esm2015/core/shared/components/asset-gallery/asset-gallery.component.js +24 -42
  68. package/esm2015/core/shared/components/configurable-input/configurable-input.component.js +13 -3
  69. package/esm2015/core/shared/components/help-tooltip/help-tooltip.component.js +1 -1
  70. package/esm2015/core/shared/components/product-multi-selector-dialog/product-multi-selector-dialog.component.js +129 -0
  71. package/esm2015/core/shared/components/product-search-input/product-search-input.component.js +104 -0
  72. package/esm2015/core/shared/components/rich-text-editor/rich-text-editor.component.js +1 -1
  73. package/esm2015/core/shared/components/select-toggle/select-toggle.component.js +5 -3
  74. package/esm2015/core/shared/dynamic-form-inputs/combination-mode-form-input/combination-mode-form-input.component.js +45 -0
  75. package/esm2015/core/shared/dynamic-form-inputs/product-multi-selector-form-input/product-multi-selector-form-input.component.js +53 -0
  76. package/esm2015/core/shared/dynamic-form-inputs/register-dynamic-input-components.js +5 -1
  77. package/esm2015/core/shared/dynamic-form-inputs/relation-form-input/asset/relation-asset-input.component.js +8 -7
  78. package/esm2015/core/shared/shared.module.js +9 -1
  79. package/esm2015/dashboard/components/dashboard/dashboard.component.js +1 -1
  80. package/esm2015/dashboard/widgets/order-summary-widget/order-summary-widget.component.js +1 -1
  81. package/esm2015/login/components/login/login.component.js +3 -3
  82. package/esm2015/marketing/components/promotion-detail/promotion-detail.component.js +2 -2
  83. package/esm2015/order/components/order-list/order-list.component.js +2 -2
  84. package/fesm2015/vendure-admin-ui-catalog.js +181 -157
  85. package/fesm2015/vendure-admin-ui-catalog.js.map +1 -1
  86. package/fesm2015/vendure-admin-ui-core.js +2156 -1691
  87. package/fesm2015/vendure-admin-ui-core.js.map +1 -1
  88. package/fesm2015/vendure-admin-ui-dashboard.js +2 -2
  89. package/fesm2015/vendure-admin-ui-login.js +2 -2
  90. package/fesm2015/vendure-admin-ui-login.js.map +1 -1
  91. package/fesm2015/vendure-admin-ui-marketing.js +1 -1
  92. package/fesm2015/vendure-admin-ui-marketing.js.map +1 -1
  93. package/fesm2015/vendure-admin-ui-order.js +1 -1
  94. package/fesm2015/vendure-admin-ui-order.js.map +1 -1
  95. package/login/vendure-admin-ui-login.metadata.json +1 -1
  96. package/marketing/vendure-admin-ui-marketing.metadata.json +1 -1
  97. package/order/vendure-admin-ui-order.metadata.json +1 -1
  98. package/package.json +2 -2
  99. package/static/i18n-messages/cs.json +9 -0
  100. package/static/i18n-messages/de.json +9 -0
  101. package/static/i18n-messages/en.json +10 -1
  102. package/static/i18n-messages/es.json +9 -0
  103. package/static/i18n-messages/fr.json +9 -0
  104. package/static/i18n-messages/it.json +9 -0
  105. package/static/i18n-messages/pl.json +9 -0
  106. package/static/i18n-messages/pt_BR.json +9 -0
  107. package/static/i18n-messages/pt_PT.json +9 -0
  108. package/static/i18n-messages/ru.json +9 -0
  109. package/static/i18n-messages/uk.json +9 -0
  110. package/static/i18n-messages/zh_Hans.json +9 -0
  111. package/static/i18n-messages/zh_Hant.json +9 -0
  112. package/static/styles/global/_forms.scss +4 -5
  113. package/static/styles/global/_overrides.scss +5 -1
  114. package/static/styles/theme/default.scss +13 -1
  115. package/static/theme.min.css +1 -1
  116. package/esm2015/catalog/components/product-search-input/product-search-input.component.js +0 -104
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Injector, isDevMode, Component, Inject, HostListener, ChangeDetectionStrategy, ComponentFactoryResolver, APP_INITIALIZER, ViewChild, ViewContainerRef, EventEmitter, Input, Output, NgModule, ChangeDetectorRef, HostBinding, ContentChild, forwardRef, TemplateRef, ContentChildren, Directive, ElementRef, Optional, SkipSelf, ViewChildren, Pipe, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2
+ import { Injectable, Injector, isDevMode, Component, Inject, HostListener, ChangeDetectionStrategy, ComponentFactoryResolver, APP_INITIALIZER, ViewChild, ViewContainerRef, EventEmitter, Input, Output, NgModule, ChangeDetectorRef, forwardRef, Optional, HostBinding, ContentChild, TemplateRef, ContentChildren, Directive, ElementRef, SkipSelf, ViewChildren, Pipe, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { Location, DOCUMENT, CommonModule, PlatformLocation } from '@angular/common';
5
5
  import { map, filter, distinctUntilChanged, skip, takeUntil, tap, take, finalize, concatMap, bufferCount, switchMap, mergeMap, mapTo, catchError, startWith, throttleTime, shareReplay, scan, debounceTime, delay } from 'rxjs/operators';
@@ -22,7 +22,7 @@ import * as i1$4 from '@angular/router';
22
22
  import { Router, NavigationEnd, PRIMARY_OUTLET, ActivatedRoute, RouterModule, ActivationStart } from '@angular/router';
23
23
  import { DEFAULT_CHANNEL_CODE, DEFAULT_AUTH_TOKEN_HEADER_KEY } from '@vendure/common/lib/shared-constants';
24
24
  import { flatten } from 'lodash';
25
- import { FormControl, NG_VALUE_ACCESSOR, FormBuilder, FormGroup, Validators, NG_VALIDATORS, NgControl, FormControlName, FormControlDirective, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
25
+ import { FormControl, FormGroup, Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormBuilder, NgControl, FormControlName, FormControlDirective, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
26
26
  import { marker } from '@biesbjerg/ngx-translate-extract-marker';
27
27
  import { setContext } from '@apollo/client/link/context';
28
28
  import { ApolloLink as ApolloLink$1 } from '@apollo/client/link/core';
@@ -252,35 +252,36 @@ class AdministratorDataService {
252
252
  }
253
253
  }
254
254
 
255
- const CONFIGURABLE_OPERATION_FRAGMENT = gql `
256
- fragment ConfigurableOperation on ConfigurableOperation {
257
- args {
258
- name
259
- value
260
- }
261
- code
262
- }
263
- `;
264
- const CONFIGURABLE_OPERATION_DEF_FRAGMENT = gql `
265
- fragment ConfigurableOperationDef on ConfigurableOperationDefinition {
266
- args {
267
- name
268
- type
269
- required
270
- defaultValue
271
- list
272
- ui
273
- label
274
- }
275
- code
276
- description
277
- }
278
- `;
279
- const ERROR_RESULT_FRAGMENT = gql `
280
- fragment ErrorResult on ErrorResult {
281
- errorCode
282
- message
283
- }
255
+ const CONFIGURABLE_OPERATION_FRAGMENT = gql `
256
+ fragment ConfigurableOperation on ConfigurableOperation {
257
+ args {
258
+ name
259
+ value
260
+ }
261
+ code
262
+ }
263
+ `;
264
+ const CONFIGURABLE_OPERATION_DEF_FRAGMENT = gql `
265
+ fragment ConfigurableOperationDef on ConfigurableOperationDefinition {
266
+ args {
267
+ name
268
+ type
269
+ required
270
+ defaultValue
271
+ list
272
+ ui
273
+ label
274
+ description
275
+ }
276
+ code
277
+ description
278
+ }
279
+ `;
280
+ const ERROR_RESULT_FRAGMENT = gql `
281
+ fragment ErrorResult on ErrorResult {
282
+ errorCode
283
+ message
284
+ }
284
285
  `;
285
286
 
286
287
  const CURRENT_USER_FRAGMENT = gql `
@@ -2650,11 +2651,28 @@ const GET_COLLECTION_CONTENTS = gql `
2650
2651
  id
2651
2652
  productId
2652
2653
  name
2654
+ sku
2653
2655
  }
2654
2656
  totalItems
2655
2657
  }
2656
2658
  }
2657
2659
  }
2660
+ `;
2661
+ const PREVIEW_COLLECTION_CONTENTS = gql `
2662
+ query PreviewCollectionContents(
2663
+ $input: PreviewCollectionVariantsInput!
2664
+ $options: ProductVariantListOptions
2665
+ ) {
2666
+ previewCollectionVariants(input: $input, options: $options) {
2667
+ items {
2668
+ id
2669
+ productId
2670
+ name
2671
+ sku
2672
+ }
2673
+ totalItems
2674
+ }
2675
+ }
2658
2676
  `;
2659
2677
 
2660
2678
  class CollectionDataService {
@@ -2710,6 +2728,9 @@ class CollectionDataService {
2710
2728
  id,
2711
2729
  });
2712
2730
  }
2731
+ previewCollectionVariants(input, options) {
2732
+ return this.baseDataService.query(PREVIEW_COLLECTION_CONTENTS, { input, options });
2733
+ }
2713
2734
  getCollectionContents(id, take = 10, skip = 0, filterTerm) {
2714
2735
  const filter = filterTerm
2715
2736
  ? { name: { contains: filterTerm } }
@@ -3315,6 +3336,7 @@ const ORDER_FRAGMENT = gql `
3315
3336
  state
3316
3337
  nextStates
3317
3338
  total
3339
+ totalWithTax
3318
3340
  currencyCode
3319
3341
  customer {
3320
3342
  id
@@ -6384,6 +6406,7 @@ class AppShellComponent {
6384
6406
  this.modalService = modalService;
6385
6407
  this.localStorageService = localStorageService;
6386
6408
  this.availableLanguages = [];
6409
+ this.hideVendureBranding = getAppConfig().hideVendureBranding;
6387
6410
  }
6388
6411
  ngOnInit() {
6389
6412
  this.userName$ = this.dataService.client
@@ -6429,8 +6452,8 @@ class AppShellComponent {
6429
6452
  AppShellComponent.decorators = [
6430
6453
  { type: Component, args: [{
6431
6454
  selector: 'vdr-app-shell',
6432
- template: "<clr-main-container>\r\n <clr-header>\r\n <div class=\"branding\">\r\n <a [routerLink]=\"['/']\"><img src=\"assets/logo-75px.png\" class=\"logo\" /></a>\r\n </div>\r\n <div class=\"header-nav\"></div>\r\n <div class=\"header-actions\">\r\n <vdr-channel-switcher *vdrIfMultichannel></vdr-channel-switcher>\r\n <vdr-user-menu [userName]=\"userName$ | async\"\r\n [uiLanguageAndLocale]=\"uiLanguageAndLocale$ | async\"\r\n [availableLanguages]=\"availableLanguages\"\r\n (selectUiLanguage)=\"selectUiLanguage()\"\r\n (logOut)=\"logOut()\"></vdr-user-menu>\r\n </div>\r\n </clr-header>\r\n <nav class=\"subnav\"><vdr-breadcrumb></vdr-breadcrumb></nav>\r\n\r\n <div class=\"content-container\">\r\n <div class=\"content-area\"><router-outlet></router-outlet></div>\r\n <vdr-main-nav></vdr-main-nav>\r\n </div>\r\n</clr-main-container>\r\n",
6433
- styles: [".branding{min-width:0}.logo{width:60px}@media screen and (min-width: 768px){vdr-breadcrumb{margin-left:10.8rem}}.header-actions{align-items:center}.content-area{position:relative}\n"]
6455
+ template: "<clr-main-container>\r\n <clr-header>\r\n <div class=\"branding\">\r\n <a [routerLink]=\"['/']\"><img src=\"assets/logo-75px.png\" class=\"logo\" /><span class=\"wordmark\" *ngIf=\"!hideVendureBranding\">vendure</span></a>\r\n </div>\r\n <div class=\"header-nav\"></div>\r\n <div class=\"header-actions\">\r\n <vdr-channel-switcher *vdrIfMultichannel></vdr-channel-switcher>\r\n <vdr-user-menu [userName]=\"userName$ | async\"\r\n [uiLanguageAndLocale]=\"uiLanguageAndLocale$ | async\"\r\n [availableLanguages]=\"availableLanguages\"\r\n (selectUiLanguage)=\"selectUiLanguage()\"\r\n (logOut)=\"logOut()\"></vdr-user-menu>\r\n </div>\r\n </clr-header>\r\n <nav class=\"subnav\"><vdr-breadcrumb></vdr-breadcrumb></nav>\r\n\r\n <div class=\"content-container\">\r\n <div class=\"content-area\"><router-outlet></router-outlet></div>\r\n <vdr-main-nav></vdr-main-nav>\r\n </div>\r\n</clr-main-container>\r\n",
6456
+ styles: [".branding{min-width:0}.logo{width:40px}.wordmark{font-weight:bold;margin-left:12px;font-size:24px;color:var(--color-primary-500)}@media screen and (min-width: 768px){vdr-breadcrumb{margin-left:10.8rem}}.header-actions{align-items:center}.content-area{position:relative}::ng-deep .header{background-image:linear-gradient(to right,var(--color-header-gradient-from),var(--color-header-gradient-to))}\n"]
6434
6457
  },] }
6435
6458
  ];
6436
6459
  AppShellComponent.ctorParameters = () => [
@@ -7404,194 +7427,260 @@ UserMenuComponent.propDecorators = {
7404
7427
 
7405
7428
  // tslint:disable
7406
7429
  const result = {
7407
- possibleTypes: {
7408
- AddFulfillmentToOrderResult: [
7409
- 'Fulfillment',
7410
- 'EmptyOrderLineSelectionError',
7411
- 'ItemsAlreadyFulfilledError',
7412
- 'InsufficientStockOnHandError',
7413
- 'InvalidFulfillmentHandlerError',
7414
- 'FulfillmentStateTransitionError',
7415
- 'CreateFulfillmentError',
7430
+ "possibleTypes": {
7431
+ "AddFulfillmentToOrderResult": [
7432
+ "Fulfillment",
7433
+ "EmptyOrderLineSelectionError",
7434
+ "ItemsAlreadyFulfilledError",
7435
+ "InsufficientStockOnHandError",
7436
+ "InvalidFulfillmentHandlerError",
7437
+ "FulfillmentStateTransitionError",
7438
+ "CreateFulfillmentError"
7416
7439
  ],
7417
- AddManualPaymentToOrderResult: ['Order', 'ManualPaymentStateError'],
7418
- AuthenticationResult: ['CurrentUser', 'InvalidCredentialsError'],
7419
- CancelOrderResult: [
7420
- 'Order',
7421
- 'EmptyOrderLineSelectionError',
7422
- 'QuantityTooGreatError',
7423
- 'MultipleOrderError',
7424
- 'CancelActiveOrderError',
7425
- 'OrderStateTransitionError',
7440
+ "AddManualPaymentToOrderResult": [
7441
+ "Order",
7442
+ "ManualPaymentStateError"
7426
7443
  ],
7427
- CreateAssetResult: ['Asset', 'MimeTypeError'],
7428
- CreateChannelResult: ['Channel', 'LanguageNotAvailableError'],
7429
- CreateCustomerResult: ['Customer', 'EmailAddressConflictError'],
7430
- CreatePromotionResult: ['Promotion', 'MissingConditionsError'],
7431
- CustomField: [
7432
- 'BooleanCustomFieldConfig',
7433
- 'DateTimeCustomFieldConfig',
7434
- 'FloatCustomFieldConfig',
7435
- 'IntCustomFieldConfig',
7436
- 'LocaleStringCustomFieldConfig',
7437
- 'RelationCustomFieldConfig',
7438
- 'StringCustomFieldConfig',
7439
- 'TextCustomFieldConfig',
7444
+ "AuthenticationResult": [
7445
+ "CurrentUser",
7446
+ "InvalidCredentialsError"
7440
7447
  ],
7441
- CustomFieldConfig: [
7442
- 'StringCustomFieldConfig',
7443
- 'LocaleStringCustomFieldConfig',
7444
- 'IntCustomFieldConfig',
7445
- 'FloatCustomFieldConfig',
7446
- 'BooleanCustomFieldConfig',
7447
- 'DateTimeCustomFieldConfig',
7448
- 'RelationCustomFieldConfig',
7449
- 'TextCustomFieldConfig',
7448
+ "CancelOrderResult": [
7449
+ "Order",
7450
+ "EmptyOrderLineSelectionError",
7451
+ "QuantityTooGreatError",
7452
+ "MultipleOrderError",
7453
+ "CancelActiveOrderError",
7454
+ "OrderStateTransitionError"
7450
7455
  ],
7451
- ErrorResult: [
7452
- 'AlreadyRefundedError',
7453
- 'CancelActiveOrderError',
7454
- 'ChannelDefaultLanguageError',
7455
- 'CouponCodeExpiredError',
7456
- 'CouponCodeInvalidError',
7457
- 'CouponCodeLimitError',
7458
- 'CreateFulfillmentError',
7459
- 'EmailAddressConflictError',
7460
- 'EmptyOrderLineSelectionError',
7461
- 'FulfillmentStateTransitionError',
7462
- 'InsufficientStockError',
7463
- 'InsufficientStockOnHandError',
7464
- 'InvalidCredentialsError',
7465
- 'InvalidFulfillmentHandlerError',
7466
- 'ItemsAlreadyFulfilledError',
7467
- 'LanguageNotAvailableError',
7468
- 'ManualPaymentStateError',
7469
- 'MimeTypeError',
7470
- 'MissingConditionsError',
7471
- 'MultipleOrderError',
7472
- 'NativeAuthStrategyError',
7473
- 'NegativeQuantityError',
7474
- 'NoChangesSpecifiedError',
7475
- 'NothingToRefundError',
7476
- 'OrderLimitError',
7477
- 'OrderModificationStateError',
7478
- 'OrderStateTransitionError',
7479
- 'PaymentMethodMissingError',
7480
- 'PaymentOrderMismatchError',
7481
- 'PaymentStateTransitionError',
7482
- 'ProductOptionInUseError',
7483
- 'QuantityTooGreatError',
7484
- 'RefundOrderStateError',
7485
- 'RefundPaymentIdMissingError',
7486
- 'RefundStateTransitionError',
7487
- 'SettlePaymentError',
7456
+ "CreateAssetResult": [
7457
+ "Asset",
7458
+ "MimeTypeError"
7488
7459
  ],
7489
- ModifyOrderResult: [
7490
- 'Order',
7491
- 'NoChangesSpecifiedError',
7492
- 'OrderModificationStateError',
7493
- 'PaymentMethodMissingError',
7494
- 'RefundPaymentIdMissingError',
7495
- 'OrderLimitError',
7496
- 'NegativeQuantityError',
7497
- 'InsufficientStockError',
7498
- 'CouponCodeExpiredError',
7499
- 'CouponCodeInvalidError',
7500
- 'CouponCodeLimitError',
7460
+ "CreateChannelResult": [
7461
+ "Channel",
7462
+ "LanguageNotAvailableError"
7501
7463
  ],
7502
- NativeAuthenticationResult: ['CurrentUser', 'InvalidCredentialsError', 'NativeAuthStrategyError'],
7503
- Node: [
7504
- 'Address',
7505
- 'Administrator',
7506
- 'Allocation',
7507
- 'Asset',
7508
- 'AuthenticationMethod',
7509
- 'Cancellation',
7510
- 'Channel',
7511
- 'Collection',
7512
- 'Country',
7513
- 'Customer',
7514
- 'CustomerGroup',
7515
- 'Facet',
7516
- 'FacetValue',
7517
- 'Fulfillment',
7518
- 'HistoryEntry',
7519
- 'Job',
7520
- 'Order',
7521
- 'OrderItem',
7522
- 'OrderLine',
7523
- 'OrderModification',
7524
- 'Payment',
7525
- 'PaymentMethod',
7526
- 'Product',
7527
- 'ProductOption',
7528
- 'ProductOptionGroup',
7529
- 'ProductVariant',
7530
- 'Promotion',
7531
- 'Refund',
7532
- 'Release',
7533
- 'Return',
7534
- 'Role',
7535
- 'Sale',
7536
- 'ShippingMethod',
7537
- 'StockAdjustment',
7538
- 'Surcharge',
7539
- 'Tag',
7540
- 'TaxCategory',
7541
- 'TaxRate',
7542
- 'User',
7543
- 'Zone',
7464
+ "CreateCustomerResult": [
7465
+ "Customer",
7466
+ "EmailAddressConflictError"
7544
7467
  ],
7545
- PaginatedList: [
7546
- 'AdministratorList',
7547
- 'AssetList',
7548
- 'CollectionList',
7549
- 'CountryList',
7550
- 'CustomerGroupList',
7551
- 'CustomerList',
7552
- 'FacetList',
7553
- 'HistoryEntryList',
7554
- 'JobList',
7555
- 'OrderList',
7556
- 'PaymentMethodList',
7557
- 'ProductList',
7558
- 'ProductVariantList',
7559
- 'PromotionList',
7560
- 'RoleList',
7561
- 'ShippingMethodList',
7562
- 'TagList',
7563
- 'TaxRateList',
7468
+ "CreatePromotionResult": [
7469
+ "Promotion",
7470
+ "MissingConditionsError"
7564
7471
  ],
7565
- RefundOrderResult: [
7566
- 'Refund',
7567
- 'QuantityTooGreatError',
7568
- 'NothingToRefundError',
7569
- 'OrderStateTransitionError',
7570
- 'MultipleOrderError',
7571
- 'PaymentOrderMismatchError',
7572
- 'RefundOrderStateError',
7573
- 'AlreadyRefundedError',
7574
- 'RefundStateTransitionError',
7472
+ "CustomField": [
7473
+ "BooleanCustomFieldConfig",
7474
+ "DateTimeCustomFieldConfig",
7475
+ "FloatCustomFieldConfig",
7476
+ "IntCustomFieldConfig",
7477
+ "LocaleStringCustomFieldConfig",
7478
+ "RelationCustomFieldConfig",
7479
+ "StringCustomFieldConfig",
7480
+ "TextCustomFieldConfig"
7575
7481
  ],
7576
- RemoveOptionGroupFromProductResult: ['Product', 'ProductOptionInUseError'],
7577
- SearchResultPrice: ['PriceRange', 'SinglePrice'],
7578
- SettlePaymentResult: [
7579
- 'Payment',
7580
- 'SettlePaymentError',
7581
- 'PaymentStateTransitionError',
7582
- 'OrderStateTransitionError',
7482
+ "CustomFieldConfig": [
7483
+ "StringCustomFieldConfig",
7484
+ "LocaleStringCustomFieldConfig",
7485
+ "IntCustomFieldConfig",
7486
+ "FloatCustomFieldConfig",
7487
+ "BooleanCustomFieldConfig",
7488
+ "DateTimeCustomFieldConfig",
7489
+ "RelationCustomFieldConfig",
7490
+ "TextCustomFieldConfig"
7583
7491
  ],
7584
- SettleRefundResult: ['Refund', 'RefundStateTransitionError'],
7585
- StockMovement: ['Allocation', 'Cancellation', 'Release', 'Return', 'Sale', 'StockAdjustment'],
7586
- StockMovementItem: ['StockAdjustment', 'Allocation', 'Sale', 'Cancellation', 'Return', 'Release'],
7587
- TransitionFulfillmentToStateResult: ['Fulfillment', 'FulfillmentStateTransitionError'],
7588
- TransitionOrderToStateResult: ['Order', 'OrderStateTransitionError'],
7589
- TransitionPaymentToStateResult: ['Payment', 'PaymentStateTransitionError'],
7590
- UpdateChannelResult: ['Channel', 'LanguageNotAvailableError'],
7591
- UpdateCustomerResult: ['Customer', 'EmailAddressConflictError'],
7592
- UpdateGlobalSettingsResult: ['GlobalSettings', 'ChannelDefaultLanguageError'],
7593
- UpdatePromotionResult: ['Promotion', 'MissingConditionsError'],
7594
- },
7492
+ "ErrorResult": [
7493
+ "AlreadyRefundedError",
7494
+ "CancelActiveOrderError",
7495
+ "ChannelDefaultLanguageError",
7496
+ "CouponCodeExpiredError",
7497
+ "CouponCodeInvalidError",
7498
+ "CouponCodeLimitError",
7499
+ "CreateFulfillmentError",
7500
+ "EmailAddressConflictError",
7501
+ "EmptyOrderLineSelectionError",
7502
+ "FulfillmentStateTransitionError",
7503
+ "InsufficientStockError",
7504
+ "InsufficientStockOnHandError",
7505
+ "InvalidCredentialsError",
7506
+ "InvalidFulfillmentHandlerError",
7507
+ "ItemsAlreadyFulfilledError",
7508
+ "LanguageNotAvailableError",
7509
+ "ManualPaymentStateError",
7510
+ "MimeTypeError",
7511
+ "MissingConditionsError",
7512
+ "MultipleOrderError",
7513
+ "NativeAuthStrategyError",
7514
+ "NegativeQuantityError",
7515
+ "NoChangesSpecifiedError",
7516
+ "NothingToRefundError",
7517
+ "OrderLimitError",
7518
+ "OrderModificationStateError",
7519
+ "OrderStateTransitionError",
7520
+ "PaymentMethodMissingError",
7521
+ "PaymentOrderMismatchError",
7522
+ "PaymentStateTransitionError",
7523
+ "ProductOptionInUseError",
7524
+ "QuantityTooGreatError",
7525
+ "RefundOrderStateError",
7526
+ "RefundPaymentIdMissingError",
7527
+ "RefundStateTransitionError",
7528
+ "SettlePaymentError"
7529
+ ],
7530
+ "ModifyOrderResult": [
7531
+ "Order",
7532
+ "NoChangesSpecifiedError",
7533
+ "OrderModificationStateError",
7534
+ "PaymentMethodMissingError",
7535
+ "RefundPaymentIdMissingError",
7536
+ "OrderLimitError",
7537
+ "NegativeQuantityError",
7538
+ "InsufficientStockError",
7539
+ "CouponCodeExpiredError",
7540
+ "CouponCodeInvalidError",
7541
+ "CouponCodeLimitError"
7542
+ ],
7543
+ "NativeAuthenticationResult": [
7544
+ "CurrentUser",
7545
+ "InvalidCredentialsError",
7546
+ "NativeAuthStrategyError"
7547
+ ],
7548
+ "Node": [
7549
+ "Address",
7550
+ "Administrator",
7551
+ "Allocation",
7552
+ "Asset",
7553
+ "AuthenticationMethod",
7554
+ "Cancellation",
7555
+ "Channel",
7556
+ "Collection",
7557
+ "Country",
7558
+ "Customer",
7559
+ "CustomerGroup",
7560
+ "Facet",
7561
+ "FacetValue",
7562
+ "Fulfillment",
7563
+ "HistoryEntry",
7564
+ "Job",
7565
+ "Order",
7566
+ "OrderItem",
7567
+ "OrderLine",
7568
+ "OrderModification",
7569
+ "Payment",
7570
+ "PaymentMethod",
7571
+ "Product",
7572
+ "ProductOption",
7573
+ "ProductOptionGroup",
7574
+ "ProductVariant",
7575
+ "Promotion",
7576
+ "Refund",
7577
+ "Release",
7578
+ "Return",
7579
+ "Role",
7580
+ "Sale",
7581
+ "ShippingMethod",
7582
+ "StockAdjustment",
7583
+ "Surcharge",
7584
+ "Tag",
7585
+ "TaxCategory",
7586
+ "TaxRate",
7587
+ "User",
7588
+ "Zone"
7589
+ ],
7590
+ "PaginatedList": [
7591
+ "AdministratorList",
7592
+ "AssetList",
7593
+ "CollectionList",
7594
+ "CountryList",
7595
+ "CustomerGroupList",
7596
+ "CustomerList",
7597
+ "FacetList",
7598
+ "HistoryEntryList",
7599
+ "JobList",
7600
+ "OrderList",
7601
+ "PaymentMethodList",
7602
+ "ProductList",
7603
+ "ProductVariantList",
7604
+ "PromotionList",
7605
+ "RoleList",
7606
+ "ShippingMethodList",
7607
+ "TagList",
7608
+ "TaxRateList"
7609
+ ],
7610
+ "RefundOrderResult": [
7611
+ "Refund",
7612
+ "QuantityTooGreatError",
7613
+ "NothingToRefundError",
7614
+ "OrderStateTransitionError",
7615
+ "MultipleOrderError",
7616
+ "PaymentOrderMismatchError",
7617
+ "RefundOrderStateError",
7618
+ "AlreadyRefundedError",
7619
+ "RefundStateTransitionError"
7620
+ ],
7621
+ "RemoveOptionGroupFromProductResult": [
7622
+ "Product",
7623
+ "ProductOptionInUseError"
7624
+ ],
7625
+ "SearchResultPrice": [
7626
+ "PriceRange",
7627
+ "SinglePrice"
7628
+ ],
7629
+ "SettlePaymentResult": [
7630
+ "Payment",
7631
+ "SettlePaymentError",
7632
+ "PaymentStateTransitionError",
7633
+ "OrderStateTransitionError"
7634
+ ],
7635
+ "SettleRefundResult": [
7636
+ "Refund",
7637
+ "RefundStateTransitionError"
7638
+ ],
7639
+ "StockMovement": [
7640
+ "Allocation",
7641
+ "Cancellation",
7642
+ "Release",
7643
+ "Return",
7644
+ "Sale",
7645
+ "StockAdjustment"
7646
+ ],
7647
+ "StockMovementItem": [
7648
+ "StockAdjustment",
7649
+ "Allocation",
7650
+ "Sale",
7651
+ "Cancellation",
7652
+ "Return",
7653
+ "Release"
7654
+ ],
7655
+ "TransitionFulfillmentToStateResult": [
7656
+ "Fulfillment",
7657
+ "FulfillmentStateTransitionError"
7658
+ ],
7659
+ "TransitionOrderToStateResult": [
7660
+ "Order",
7661
+ "OrderStateTransitionError"
7662
+ ],
7663
+ "TransitionPaymentToStateResult": [
7664
+ "Payment",
7665
+ "PaymentStateTransitionError"
7666
+ ],
7667
+ "UpdateChannelResult": [
7668
+ "Channel",
7669
+ "LanguageNotAvailableError"
7670
+ ],
7671
+ "UpdateCustomerResult": [
7672
+ "Customer",
7673
+ "EmailAddressConflictError"
7674
+ ],
7675
+ "UpdateGlobalSettingsResult": [
7676
+ "GlobalSettings",
7677
+ "ChannelDefaultLanguageError"
7678
+ ],
7679
+ "UpdatePromotionResult": [
7680
+ "Promotion",
7681
+ "MissingConditionsError"
7682
+ ]
7683
+ }
7595
7684
  };
7596
7685
 
7597
7686
  // Allows the introspectionResult to be imported as a named symbol
@@ -7656,7 +7745,7 @@ function getClientDefaults(localStorageService) {
7656
7745
  };
7657
7746
  }
7658
7747
 
7659
- const ɵ0$2 = (_, args, { cache }) => {
7748
+ const ɵ0$3 = (_, args, { cache }) => {
7660
7749
  return updateRequestsInFlight(cache, 1);
7661
7750
  }, ɵ1 = (_, args, { cache }) => {
7662
7751
  return updateRequestsInFlight(cache, -1);
@@ -7746,7 +7835,7 @@ const ɵ0$2 = (_, args, { cache }) => {
7746
7835
  };
7747
7836
  const clientResolvers = {
7748
7837
  Mutation: {
7749
- requestStarted: ɵ0$2,
7838
+ requestStarted: ɵ0$3,
7750
7839
  requestCompleted: ɵ1,
7751
7840
  setAsLoggedIn: ɵ2,
7752
7841
  setAsLoggedOut: ɵ3,
@@ -8170,7 +8259,7 @@ function createApollo(localStorageService, fetchAdapter, injector) {
8170
8259
  resolvers: clientResolvers,
8171
8260
  };
8172
8261
  }
8173
- const ɵ0$1 = initializeServerConfigService;
8262
+ const ɵ0$2 = initializeServerConfigService;
8174
8263
  /**
8175
8264
  * The DataModule is responsible for all API calls *and* serves as the source of truth for global app
8176
8265
  * state via the apollo-link-state package.
@@ -8196,7 +8285,7 @@ DataModule.decorators = [
8196
8285
  {
8197
8286
  provide: APP_INITIALIZER,
8198
8287
  multi: true,
8199
- useFactory: ɵ0$1,
8288
+ useFactory: ɵ0$2,
8200
8289
  deps: [ServerConfigService],
8201
8290
  },
8202
8291
  ],
@@ -8474,337 +8563,447 @@ JsonEditorFormInputComponent.propDecorators = {
8474
8563
  };
8475
8564
 
8476
8565
  /**
8477
- * @description
8478
- * An input for monetary values. Should be used with `int` type fields.
8479
- *
8480
- * @docsCategory custom-input-components
8481
- * @docsPage default-inputs
8566
+ * ConfigArg values are always stored as strings. If they are not primitives, then
8567
+ * they are JSON-encoded. This function unwraps them back into their original
8568
+ * data type.
8482
8569
  */
8483
- class CurrencyFormInputComponent {
8484
- constructor(dataService) {
8485
- this.dataService = dataService;
8486
- this.currencyCode$ = this.dataService.settings
8487
- .getActiveChannel()
8488
- .mapStream(data => data.activeChannel.currencyCode);
8570
+ function getConfigArgValue(value) {
8571
+ try {
8572
+ return value ? JSON.parse(value) : undefined;
8573
+ }
8574
+ catch (e) {
8575
+ return value;
8489
8576
  }
8490
8577
  }
8491
- CurrencyFormInputComponent.id = 'currency-form-input';
8492
- CurrencyFormInputComponent.decorators = [
8493
- { type: Component, args: [{
8494
- selector: 'vdr-currency-form-input',
8495
- template: "<vdr-currency-input\r\n [formControl]=\"formControl\"\r\n [readonly]=\"readonly\"\r\n [currencyCode]=\"currencyCode$ | async\"\r\n></vdr-currency-input>\r\n",
8496
- changeDetection: ChangeDetectionStrategy.OnPush,
8497
- styles: [""]
8498
- },] }
8499
- ];
8500
- CurrencyFormInputComponent.ctorParameters = () => [
8501
- { type: DataService }
8502
- ];
8503
- CurrencyFormInputComponent.propDecorators = {
8504
- readonly: [{ type: Input }]
8505
- };
8506
-
8578
+ function encodeConfigArgValue(value) {
8579
+ return Array.isArray(value) ? JSON.stringify(value) : (value !== null && value !== void 0 ? value : '').toString();
8580
+ }
8507
8581
  /**
8508
- * @description
8509
- * Allows the selection of a Customer via an autocomplete select input.
8510
- * Should be used with `ID` type fields which represent Customer IDs.
8511
- *
8512
- * @docsCategory custom-input-components
8513
- * @docsPage default-inputs
8582
+ * Creates an empty ConfigurableOperation object based on the definition.
8514
8583
  */
8515
- class CustomerGroupFormInputComponent {
8516
- constructor(dataService) {
8517
- this.dataService = dataService;
8518
- }
8519
- ngOnInit() {
8520
- this.customerGroups$ = this.dataService.customer
8521
- .getCustomerGroupList({
8522
- take: 1000,
8523
- })
8524
- .mapSingle(res => res.customerGroups.items)
8525
- .pipe(startWith([]));
8526
- }
8527
- selectGroup(group) {
8528
- this.formControl.setValue(group.id);
8529
- }
8584
+ function configurableDefinitionToInstance(def) {
8585
+ return Object.assign(Object.assign({}, def), { args: def.args.map(arg => {
8586
+ return Object.assign(Object.assign({}, arg), { value: getDefaultConfigArgValue(arg) });
8587
+ }) });
8530
8588
  }
8531
- CustomerGroupFormInputComponent.id = 'customer-group-form-input';
8532
- CustomerGroupFormInputComponent.decorators = [
8533
- { type: Component, args: [{
8534
- selector: 'vdr-customer-group-form-input',
8535
- template: "<ng-select\r\n [items]=\"customerGroups$ | async\"\r\n appendTo=\"body\"\r\n [addTag]=\"false\"\r\n [multiple]=\"false\"\r\n bindValue=\"id\"\r\n [clearable]=\"true\"\r\n [searchable]=\"false\"\r\n [ngModel]=\"formControl.value\"\r\n (change)=\"selectGroup($event)\"\r\n>\r\n <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\r\n <vdr-chip [colorFrom]=\"item.id\">{{ item.name }}</vdr-chip>\r\n </ng-template>\r\n <ng-template ng-option-tmp let-item=\"item\">\r\n <vdr-chip [colorFrom]=\"item.id\">{{ item.name }}</vdr-chip>\r\n </ng-template>\r\n</ng-select>\r\n",
8536
- changeDetection: ChangeDetectionStrategy.OnPush,
8537
- styles: [""]
8538
- },] }
8539
- ];
8540
- CustomerGroupFormInputComponent.ctorParameters = () => [
8541
- { type: DataService }
8542
- ];
8543
- CustomerGroupFormInputComponent.propDecorators = {
8544
- readonly: [{ type: Input }]
8545
- };
8546
-
8547
8589
  /**
8548
- * @description
8549
- * Allows selection of a datetime. Default input for `datetime` type fields.
8550
- *
8551
- * @docsCategory custom-input-components
8552
- * @docsPage default-inputs
8590
+ * Converts an object of the type:
8591
+ * ```
8592
+ * {
8593
+ * code: 'my-operation',
8594
+ * args: {
8595
+ * someProperty: 'foo'
8596
+ * }
8597
+ * }
8598
+ * ```
8599
+ * to the format defined by the ConfigurableOperationInput GraphQL input type:
8600
+ * ```
8601
+ * {
8602
+ * code: 'my-operation',
8603
+ * args: [
8604
+ * { name: 'someProperty', value: 'foo' }
8605
+ * ]
8606
+ * }
8607
+ * ```
8553
8608
  */
8554
- class DateFormInputComponent {
8555
- get min() {
8556
- var _a;
8557
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.min) || this.config.min;
8609
+ function toConfigurableOperationInput(operation, formValueOperations) {
8610
+ return {
8611
+ code: operation.code,
8612
+ arguments: Object.values(formValueOperations.args || {}).map((value, j) => ({
8613
+ name: operation.args[j].name,
8614
+ value: value.hasOwnProperty('value')
8615
+ ? encodeConfigArgValue(value.value)
8616
+ : encodeConfigArgValue(value),
8617
+ })),
8618
+ };
8619
+ }
8620
+ function configurableOperationValueIsValid(def, value) {
8621
+ if (!def || !value) {
8622
+ return false;
8558
8623
  }
8559
- get max() {
8560
- var _a;
8561
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.max) || this.config.max;
8624
+ if (def.code !== value.code) {
8625
+ return false;
8562
8626
  }
8563
- get yearRange() {
8564
- var _a;
8565
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.yearRange) || this.config.yearRange;
8627
+ for (const argDef of def.args) {
8628
+ const argVal = value.args[argDef.name];
8629
+ if (argDef.required && (argVal == null || argVal === '' || argVal === '0')) {
8630
+ return false;
8631
+ }
8566
8632
  }
8633
+ return true;
8567
8634
  }
8568
- DateFormInputComponent.id = 'date-form-input';
8569
- DateFormInputComponent.decorators = [
8570
- { type: Component, args: [{
8571
- selector: 'vdr-date-form-input',
8572
- template: "<vdr-datetime-picker\r\n [formControl]=\"formControl\"\r\n [min]=\"min\"\r\n [max]=\"max\"\r\n [yearRange]=\"yearRange\"\r\n [readonly]=\"readonly\"\r\n>\r\n</vdr-datetime-picker>\r\n",
8573
- changeDetection: ChangeDetectionStrategy.OnPush,
8574
- styles: [""]
8575
- },] }
8576
- ];
8577
- DateFormInputComponent.propDecorators = {
8578
- readonly: [{ type: Input }]
8579
- };
8580
-
8581
8635
  /**
8582
- * @description
8583
- * Allows the selection of multiple FacetValues via an autocomplete select input.
8584
- * Should be used with `ID` type **list** fields which represent FacetValue IDs.
8585
- *
8586
- * @docsCategory custom-input-components
8587
- * @docsPage default-inputs
8636
+ * Returns a default value based on the type of the config arg.
8588
8637
  */
8589
- class FacetValueFormInputComponent {
8590
- constructor(dataService) {
8591
- this.dataService = dataService;
8592
- this.isListInput = true;
8638
+ function getDefaultConfigArgValue(arg) {
8639
+ if (arg.list) {
8640
+ return [];
8593
8641
  }
8594
- ngOnInit() {
8595
- this.facets$ = this.dataService.facet
8596
- .getAllFacets()
8597
- .mapSingle(data => data.facets.items)
8598
- .pipe(shareReplay(1));
8642
+ if (arg.defaultValue) {
8643
+ return arg.defaultValue;
8599
8644
  }
8600
- }
8601
- FacetValueFormInputComponent.id = 'facet-value-form-input';
8602
- FacetValueFormInputComponent.decorators = [
8603
- { type: Component, args: [{
8604
- selector: 'vdr-facet-value-form-input',
8605
- template: "<vdr-facet-value-selector\r\n *ngIf=\"facets$ | async as facets\"\r\n [readonly]=\"readonly\"\r\n [facets]=\"facets\"\r\n [formControl]=\"formControl\"\r\n></vdr-facet-value-selector>\r\n",
8606
- changeDetection: ChangeDetectionStrategy.OnPush,
8607
- styles: [""]
8608
- },] }
8609
- ];
8610
- FacetValueFormInputComponent.ctorParameters = () => [
8611
- { type: DataService }
8612
- ];
8645
+ const type = arg.type;
8646
+ switch (type) {
8647
+ case 'string':
8648
+ case 'datetime':
8649
+ case 'float':
8650
+ case 'ID':
8651
+ case 'int':
8652
+ return null;
8653
+ case 'boolean':
8654
+ return false;
8655
+ default:
8656
+ assertNever(type);
8657
+ }
8658
+ }
8613
8659
 
8614
8660
  /**
8615
- * @description
8616
- * Displays a number input. Default input for `int` and `float` type fields.
8617
- *
8618
- * @docsCategory custom-input-components
8619
- * @docsPage default-inputs
8661
+ * Interpolates the description of an ConfigurableOperation with the given values.
8620
8662
  */
8621
- class NumberFormInputComponent {
8622
- get prefix() {
8623
- var _a;
8624
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.prefix) || this.config.prefix;
8663
+ function interpolateDescription(operation, values) {
8664
+ if (!operation) {
8665
+ return '';
8625
8666
  }
8626
- get suffix() {
8627
- var _a;
8628
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.suffix) || this.config.suffix;
8667
+ const templateString = operation.description;
8668
+ const interpolated = templateString.replace(/{\s*([a-zA-Z0-9]+)\s*}/gi, (substring, argName) => {
8669
+ const normalizedArgName = argName.toLowerCase();
8670
+ const value = values[normalizedArgName];
8671
+ if (value == null) {
8672
+ return '_';
8673
+ }
8674
+ let formatted = value;
8675
+ const argDef = operation.args.find(arg => arg.name === normalizedArgName);
8676
+ if (argDef && argDef.type === 'int' && argDef.ui && argDef.ui.component === 'currency-form-input') {
8677
+ formatted = value / 100;
8678
+ }
8679
+ if (argDef && argDef.type === 'datetime' && value instanceof Date) {
8680
+ formatted = value.toLocaleDateString();
8681
+ }
8682
+ return formatted;
8683
+ });
8684
+ return interpolated;
8685
+ }
8686
+
8687
+ /**
8688
+ * A form input which renders a card with the internal form fields of the given ConfigurableOperation.
8689
+ */
8690
+ class ConfigurableInputComponent {
8691
+ constructor() {
8692
+ this.readonly = false;
8693
+ this.removable = true;
8694
+ this.position = 0;
8695
+ this.remove = new EventEmitter();
8696
+ this.argValues = {};
8697
+ this.form = new FormGroup({});
8698
+ this.positionChangeSubject = new BehaviorSubject(0);
8629
8699
  }
8630
- get min() {
8631
- var _a;
8632
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.min) || this.config.min;
8700
+ interpolateDescription() {
8701
+ if (this.operationDefinition) {
8702
+ return interpolateDescription(this.operationDefinition, this.form.value);
8703
+ }
8704
+ else {
8705
+ return '';
8706
+ }
8633
8707
  }
8634
- get max() {
8635
- var _a;
8636
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.max) || this.config.max;
8708
+ ngOnInit() {
8709
+ this.positionChange$ = this.positionChangeSubject.asObservable();
8637
8710
  }
8638
- get step() {
8711
+ ngOnChanges(changes) {
8712
+ if ('operation' in changes || 'operationDefinition' in changes) {
8713
+ this.createForm();
8714
+ }
8715
+ if ('position' in changes) {
8716
+ this.positionChangeSubject.next(this.position);
8717
+ }
8718
+ }
8719
+ ngOnDestroy() {
8720
+ if (this.subscription) {
8721
+ this.subscription.unsubscribe();
8722
+ }
8723
+ }
8724
+ registerOnChange(fn) {
8725
+ this.onChange = fn;
8726
+ }
8727
+ registerOnTouched(fn) {
8728
+ this.onTouch = fn;
8729
+ }
8730
+ setDisabledState(isDisabled) {
8731
+ if (isDisabled) {
8732
+ this.form.disable();
8733
+ }
8734
+ else {
8735
+ this.form.enable();
8736
+ }
8737
+ }
8738
+ writeValue(value) {
8739
+ if (value) {
8740
+ this.form.patchValue(value);
8741
+ }
8742
+ }
8743
+ trackByName(index, arg) {
8744
+ return arg.name;
8745
+ }
8746
+ getArgDef(arg) {
8639
8747
  var _a;
8640
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.step) || this.config.step;
8748
+ return (_a = this.operationDefinition) === null || _a === void 0 ? void 0 : _a.args.find(a => a.name === arg.name);
8749
+ }
8750
+ createForm() {
8751
+ var _a, _b;
8752
+ if (!this.operation) {
8753
+ return;
8754
+ }
8755
+ if (this.subscription) {
8756
+ this.subscription.unsubscribe();
8757
+ }
8758
+ this.form = new FormGroup({});
8759
+ this.form.__id = Math.random().toString(36).substr(10);
8760
+ if (this.operation.args) {
8761
+ for (const arg of ((_a = this.operationDefinition) === null || _a === void 0 ? void 0 : _a.args) || []) {
8762
+ let value = (_b = this.operation.args.find(a => a.name === arg.name)) === null || _b === void 0 ? void 0 : _b.value;
8763
+ if (value === undefined) {
8764
+ value = getDefaultConfigArgValue(arg);
8765
+ }
8766
+ const validators = arg.list ? undefined : arg.required ? Validators.required : undefined;
8767
+ this.form.addControl(arg.name, new FormControl(value, validators));
8768
+ }
8769
+ }
8770
+ this.subscription = this.form.valueChanges.subscribe(value => {
8771
+ if (this.onChange) {
8772
+ this.onChange({
8773
+ code: this.operation && this.operation.code,
8774
+ args: value,
8775
+ });
8776
+ }
8777
+ if (this.onTouch) {
8778
+ this.onTouch();
8779
+ }
8780
+ });
8781
+ }
8782
+ validate(c) {
8783
+ if (this.form.invalid) {
8784
+ return {
8785
+ required: true,
8786
+ };
8787
+ }
8788
+ return null;
8641
8789
  }
8642
8790
  }
8643
- NumberFormInputComponent.id = 'number-form-input';
8644
- NumberFormInputComponent.decorators = [
8791
+ ConfigurableInputComponent.decorators = [
8645
8792
  { type: Component, args: [{
8646
- selector: 'vdr-number-form-input',
8647
- template: "<vdr-affixed-input\r\n [suffix]=\"suffix\"\r\n [prefix]=\"prefix\"\r\n>\r\n <input\r\n type=\"number\"\r\n [readonly]=\"readonly\"\r\n [min]=\"min\"\r\n [max]=\"max\"\r\n [step]=\"step\"\r\n [formControl]=\"formControl\"\r\n />\r\n</vdr-affixed-input>\r\n",
8793
+ selector: 'vdr-configurable-input',
8794
+ template: "<div class=\"card\" *ngIf=\"operation\">\r\n <div class=\"card-block\">{{ interpolateDescription() }}</div>\r\n <div class=\"card-block\" *ngIf=\"operation.args?.length\">\r\n <form [formGroup]=\"form\" *ngIf=\"operation\" class=\"operation-inputs\">\r\n <div *ngFor=\"let arg of operation.args; trackBy: trackByName\" class=\"arg-row\">\r\n <ng-container *ngIf=\"form.get(arg.name) && getArgDef(arg) as argDef\">\r\n <label class=\"clr-control-label\">{{ argDef.label || (arg.name | sentenceCase) }}</label>\r\n <vdr-help-tooltip\r\n class=\"mr3\"\r\n *ngIf=\"argDef.description\"\r\n [content]=\"argDef.description\"\r\n ></vdr-help-tooltip>\r\n <vdr-dynamic-form-input\r\n [def]=\"getArgDef(arg)\"\r\n [readonly]=\"readonly\"\r\n [control]=\"form.get(arg.name)\"\r\n [formControlName]=\"arg.name\"\r\n ></vdr-dynamic-form-input>\r\n </ng-container>\r\n </div>\r\n </form>\r\n </div>\r\n <div class=\"card-footer\" *ngIf=\"!readonly && removable\">\r\n <button class=\"btn btn-sm btn-link btn-warning\" (click)=\"remove.emit(operation)\">\r\n <clr-icon shape=\"times\"></clr-icon>\r\n {{ 'common.remove' | translate }}\r\n </button>\r\n </div>\r\n</div>\r\n",
8648
8795
  changeDetection: ChangeDetectionStrategy.OnPush,
8649
- styles: [""]
8796
+ providers: [
8797
+ {
8798
+ provide: NG_VALUE_ACCESSOR,
8799
+ useExisting: ConfigurableInputComponent,
8800
+ multi: true,
8801
+ },
8802
+ {
8803
+ provide: NG_VALIDATORS,
8804
+ useExisting: forwardRef(() => ConfigurableInputComponent),
8805
+ multi: true,
8806
+ },
8807
+ ],
8808
+ styles: [":host{display:block;margin-bottom:12px}:host>.card{margin-top:6px}.operation-inputs{padding-top:0}.operation-inputs .arg-row:not(:last-child){margin-bottom:12px}.operation-inputs .arg-row{display:flex;flex-wrap:wrap;align-items:center}.operation-inputs .arg-row label{margin-right:6px}.operation-inputs .hidden{display:none}.operation-inputs label{min-width:130px;display:inline-block}\n"]
8650
8809
  },] }
8651
8810
  ];
8652
- NumberFormInputComponent.propDecorators = {
8653
- readonly: [{ type: Input }]
8811
+ ConfigurableInputComponent.propDecorators = {
8812
+ operation: [{ type: Input }],
8813
+ operationDefinition: [{ type: Input }],
8814
+ readonly: [{ type: Input }],
8815
+ removable: [{ type: Input }],
8816
+ position: [{ type: Input }],
8817
+ remove: [{ type: Output }]
8654
8818
  };
8655
8819
 
8656
8820
  /**
8657
8821
  * @description
8658
- * Displays a password text input. Should be used with `string` type fields.
8822
+ * A special input used to display the "Combination mode" AND/OR toggle.
8659
8823
  *
8660
8824
  * @docsCategory custom-input-components
8661
8825
  * @docsPage default-inputs
8662
8826
  */
8663
- class PasswordFormInputComponent {
8827
+ class CombinationModeFormInputComponent {
8828
+ constructor(configurableInputComponent) {
8829
+ this.configurableInputComponent = configurableInputComponent;
8830
+ }
8831
+ ngOnInit() {
8832
+ const selectable$ = this.configurableInputComponent
8833
+ ? this.configurableInputComponent.positionChange$.pipe(map(position => 0 < position))
8834
+ : of(true);
8835
+ this.selectable$ = selectable$.pipe(tap(selectable => {
8836
+ if (!selectable) {
8837
+ this.formControl.setValue(true, { emitEvent: false });
8838
+ }
8839
+ }));
8840
+ }
8841
+ setCombinationModeAnd() {
8842
+ this.formControl.setValue(true);
8843
+ }
8844
+ setCombinationModeOr() {
8845
+ this.formControl.setValue(false);
8846
+ }
8664
8847
  }
8665
- PasswordFormInputComponent.id = 'password-form-input';
8666
- PasswordFormInputComponent.decorators = [
8848
+ CombinationModeFormInputComponent.id = 'combination-mode-form-input';
8849
+ CombinationModeFormInputComponent.decorators = [
8667
8850
  { type: Component, args: [{
8668
- selector: 'vdr-password-form-input',
8669
- template: "<input\r\n type=\"password\"\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n/>\r\n",
8851
+ selector: 'vdr-combination-mode-form-input',
8852
+ template: "<ng-container *ngIf=\"selectable$ | async; else default\">\r\n <div class=\"btn-group btn-outline-primary btn-sm mode-select\">\r\n <button\r\n class=\"btn\"\r\n (click)=\"setCombinationModeAnd()\"\r\n [class.btn-primary]=\"formControl.value === true\"\r\n >\r\n {{ 'common.boolean-and' | translate }}\r\n </button>\r\n <button\r\n class=\"btn\"\r\n (click)=\"setCombinationModeOr()\"\r\n [class.btn-primary]=\"formControl.value === false\"\r\n >\r\n {{ 'common.boolean-or' | translate }}\r\n </button>\r\n </div>\r\n</ng-container>\r\n<ng-template #default>\r\n <small>{{ 'common.not-applicable' | translate }}</small>\r\n</ng-template>\r\n",
8670
8853
  changeDetection: ChangeDetectionStrategy.OnPush,
8671
- styles: [""]
8854
+ styles: [".mode-select{text-transform:uppercase}\n"]
8672
8855
  },] }
8856
+ ];
8857
+ CombinationModeFormInputComponent.ctorParameters = () => [
8858
+ { type: ConfigurableInputComponent, decorators: [{ type: Optional }] }
8673
8859
  ];
8674
8860
 
8675
8861
  /**
8676
8862
  * @description
8677
- * Allows the selection of multiple ProductVariants via an autocomplete select input.
8678
- * Should be used with `ID` type **list** fields which represent ProductVariant IDs.
8863
+ * An input for monetary values. Should be used with `int` type fields.
8679
8864
  *
8680
8865
  * @docsCategory custom-input-components
8681
8866
  * @docsPage default-inputs
8682
8867
  */
8683
- class ProductSelectorFormInputComponent {
8868
+ class CurrencyFormInputComponent {
8684
8869
  constructor(dataService) {
8685
8870
  this.dataService = dataService;
8686
- this.isListInput = true;
8687
- }
8688
- ngOnInit() {
8689
- this.formControl.setValidators([
8690
- control => {
8691
- if (!control.value || !control.value.length) {
8692
- return {
8693
- atLeastOne: { length: control.value.length },
8694
- };
8695
- }
8696
- return null;
8697
- },
8698
- ]);
8699
- this.selection$ = this.formControl.valueChanges.pipe(startWith(this.formControl.value), switchMap(value => {
8700
- if (Array.isArray(value) && 0 < value.length) {
8701
- return forkJoin(value.map(id => this.dataService.product
8702
- .getProductVariant(id)
8703
- .mapSingle(data => data.productVariant)));
8704
- }
8705
- return of([]);
8706
- }), map(variants => variants.filter(notNullOrUndefined)));
8707
- }
8708
- addProductVariant(product) {
8709
- const value = this.formControl.value;
8710
- this.formControl.setValue([...new Set([...value, product.productVariantId])]);
8711
- }
8712
- removeProductVariant(id) {
8713
- const value = this.formControl.value;
8714
- this.formControl.setValue(value.filter(_id => _id !== id));
8871
+ this.currencyCode$ = this.dataService.settings
8872
+ .getActiveChannel()
8873
+ .mapStream(data => data.activeChannel.currencyCode);
8715
8874
  }
8716
8875
  }
8717
- ProductSelectorFormInputComponent.id = 'product-selector-form-input';
8718
- ProductSelectorFormInputComponent.decorators = [
8876
+ CurrencyFormInputComponent.id = 'currency-form-input';
8877
+ CurrencyFormInputComponent.decorators = [
8719
8878
  { type: Component, args: [{
8720
- selector: 'vdr-product-selector-form-input',
8721
- template: "<ul class=\"list-unstyled\">\r\n <li *ngFor=\"let variant of selection$ | async\" class=\"variant\">\r\n <div class=\"thumb\">\r\n <img [src]=\"variant.product.featuredAsset | assetPreview: 32\" />\r\n </div>\r\n <div class=\"detail\">\r\n <div>{{ variant.name }}</div>\r\n <div class=\"sku\">{{ variant.sku }}</div>\r\n </div>\r\n <div class=\"flex-spacer\"></div>\r\n <button\r\n class=\"btn btn-link btn-sm btn-warning\"\r\n (click)=\"removeProductVariant(variant.id)\"\r\n [title]=\"'common.remove-item-from-list' | translate\"\r\n >\r\n <clr-icon shape=\"times\"></clr-icon>\r\n </button>\r\n </li>\r\n</ul>\r\n<vdr-product-selector (productSelected)=\"addProductVariant($event)\"></vdr-product-selector>\r\n",
8879
+ selector: 'vdr-currency-form-input',
8880
+ template: "<vdr-currency-input\r\n [formControl]=\"formControl\"\r\n [readonly]=\"readonly\"\r\n [currencyCode]=\"currencyCode$ | async\"\r\n></vdr-currency-input>\r\n",
8722
8881
  changeDetection: ChangeDetectionStrategy.OnPush,
8723
- styles: [".variant{margin-bottom:6px;display:flex;align-items:center;transition:background-color .2s}.variant:hover{background-color:var(--color-component-bg-200)}.thumb{margin-right:6px}.sku{color:var(--color-grey-400);font-size:smaller;line-height:1em}\n"]
8882
+ styles: [""]
8724
8883
  },] }
8725
8884
  ];
8726
- ProductSelectorFormInputComponent.ctorParameters = () => [
8885
+ CurrencyFormInputComponent.ctorParameters = () => [
8727
8886
  { type: DataService }
8728
- ];
8887
+ ];
8888
+ CurrencyFormInputComponent.propDecorators = {
8889
+ readonly: [{ type: Input }]
8890
+ };
8729
8891
 
8730
8892
  /**
8731
8893
  * @description
8732
- * The default input component for `relation` type custom fields. Allows the selection
8733
- * of a ProductVariant, Product, Customer or Asset. For other entity types, a custom
8734
- * implementation will need to be defined. See {@link registerFormInputComponent}.
8894
+ * Allows the selection of a Customer via an autocomplete select input.
8895
+ * Should be used with `ID` type fields which represent Customer IDs.
8735
8896
  *
8736
8897
  * @docsCategory custom-input-components
8737
8898
  * @docsPage default-inputs
8738
8899
  */
8739
- class RelationFormInputComponent {
8900
+ class CustomerGroupFormInputComponent {
8901
+ constructor(dataService) {
8902
+ this.dataService = dataService;
8903
+ }
8904
+ ngOnInit() {
8905
+ this.customerGroups$ = this.dataService.customer
8906
+ .getCustomerGroupList({
8907
+ take: 1000,
8908
+ })
8909
+ .mapSingle(res => res.customerGroups.items)
8910
+ .pipe(startWith([]));
8911
+ }
8912
+ selectGroup(group) {
8913
+ this.formControl.setValue(group.id);
8914
+ }
8740
8915
  }
8741
- RelationFormInputComponent.id = 'relation-form-input';
8742
- RelationFormInputComponent.decorators = [
8916
+ CustomerGroupFormInputComponent.id = 'customer-group-form-input';
8917
+ CustomerGroupFormInputComponent.decorators = [
8743
8918
  { type: Component, args: [{
8744
- selector: 'vdr-relation-form-input',
8745
- template: "<div [ngSwitch]=\"config.entity\">\r\n <vdr-relation-asset-input\r\n *ngSwitchCase=\"'Asset'\"\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-asset-input>\r\n <vdr-relation-product-input\r\n *ngSwitchCase=\"'Product'\"\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-product-input>\r\n <vdr-relation-customer-input\r\n *ngSwitchCase=\"'Customer'\"\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-customer-input>\r\n <vdr-relation-product-variant-input\r\n *ngSwitchCase=\"'ProductVariant'\"\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-product-variant-input>\r\n <ng-template ngSwitchDefault>\r\n <vdr-relation-generic-input\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-generic-input>\r\n </ng-template>\r\n</div>\r\n",
8919
+ selector: 'vdr-customer-group-form-input',
8920
+ template: "<ng-select\r\n [items]=\"customerGroups$ | async\"\r\n appendTo=\"body\"\r\n [addTag]=\"false\"\r\n [multiple]=\"false\"\r\n bindValue=\"id\"\r\n [clearable]=\"true\"\r\n [searchable]=\"false\"\r\n [ngModel]=\"formControl.value\"\r\n (change)=\"selectGroup($event)\"\r\n>\r\n <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\r\n <vdr-chip [colorFrom]=\"item.id\">{{ item.name }}</vdr-chip>\r\n </ng-template>\r\n <ng-template ng-option-tmp let-item=\"item\">\r\n <vdr-chip [colorFrom]=\"item.id\">{{ item.name }}</vdr-chip>\r\n </ng-template>\r\n</ng-select>\r\n",
8746
8921
  changeDetection: ChangeDetectionStrategy.OnPush,
8747
- styles: [":host{display:block;background-color:var(--color-component-bg-200);padding:3px}\n"]
8922
+ styles: [""]
8748
8923
  },] }
8749
8924
  ];
8750
- RelationFormInputComponent.propDecorators = {
8925
+ CustomerGroupFormInputComponent.ctorParameters = () => [
8926
+ { type: DataService }
8927
+ ];
8928
+ CustomerGroupFormInputComponent.propDecorators = {
8751
8929
  readonly: [{ type: Input }]
8752
8930
  };
8753
8931
 
8754
8932
  /**
8755
8933
  * @description
8756
- * Uses the {@link RichTextEditorComponent} as in input for `text` type fields.
8934
+ * Allows selection of a datetime. Default input for `datetime` type fields.
8757
8935
  *
8758
8936
  * @docsCategory custom-input-components
8759
8937
  * @docsPage default-inputs
8760
8938
  */
8761
- class RichTextFormInputComponent {
8939
+ class DateFormInputComponent {
8940
+ get min() {
8941
+ var _a;
8942
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.min) || this.config.min;
8943
+ }
8944
+ get max() {
8945
+ var _a;
8946
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.max) || this.config.max;
8947
+ }
8948
+ get yearRange() {
8949
+ var _a;
8950
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.yearRange) || this.config.yearRange;
8951
+ }
8762
8952
  }
8763
- RichTextFormInputComponent.id = 'rich-text-form-input';
8764
- RichTextFormInputComponent.decorators = [
8953
+ DateFormInputComponent.id = 'date-form-input';
8954
+ DateFormInputComponent.decorators = [
8765
8955
  { type: Component, args: [{
8766
- selector: 'vdr-rich-text-form-input',
8767
- template: "<vdr-rich-text-editor\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n></vdr-rich-text-editor>\r\n",
8956
+ selector: 'vdr-date-form-input',
8957
+ template: "<vdr-datetime-picker\r\n [formControl]=\"formControl\"\r\n [min]=\"min\"\r\n [max]=\"max\"\r\n [yearRange]=\"yearRange\"\r\n [readonly]=\"readonly\"\r\n>\r\n</vdr-datetime-picker>\r\n",
8768
8958
  changeDetection: ChangeDetectionStrategy.OnPush,
8769
- styles: [":host textarea{resize:both;height:6rem;width:100%}\n"]
8959
+ styles: [""]
8770
8960
  },] }
8771
- ];
8961
+ ];
8962
+ DateFormInputComponent.propDecorators = {
8963
+ readonly: [{ type: Input }]
8964
+ };
8772
8965
 
8773
8966
  /**
8774
8967
  * @description
8775
- * Uses a select input to allow the selection of a string value. Should be used with
8776
- * `string` type fields with options.
8968
+ * Allows the selection of multiple FacetValues via an autocomplete select input.
8969
+ * Should be used with `ID` type **list** fields which represent FacetValue IDs.
8777
8970
  *
8778
8971
  * @docsCategory custom-input-components
8779
8972
  * @docsPage default-inputs
8780
8973
  */
8781
- class SelectFormInputComponent {
8782
- get options() {
8783
- var _a;
8784
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.options) || this.config.options;
8974
+ class FacetValueFormInputComponent {
8975
+ constructor(dataService) {
8976
+ this.dataService = dataService;
8977
+ this.isListInput = true;
8978
+ }
8979
+ ngOnInit() {
8980
+ this.facets$ = this.dataService.facet
8981
+ .getAllFacets()
8982
+ .mapSingle(data => data.facets.items)
8983
+ .pipe(shareReplay(1));
8785
8984
  }
8786
8985
  }
8787
- SelectFormInputComponent.id = 'select-form-input';
8788
- SelectFormInputComponent.decorators = [
8986
+ FacetValueFormInputComponent.id = 'facet-value-form-input';
8987
+ FacetValueFormInputComponent.decorators = [
8789
8988
  { type: Component, args: [{
8790
- selector: 'vdr-select-form-input',
8791
- template: "<select clrSelect [formControl]=\"formControl\" [vdrDisabled]=\"readonly\">\r\n <option *ngIf=\"config.nullable\" [ngValue]=\"null\"></option>\r\n <option *ngFor=\"let option of options\" [ngValue]=\"option.value\">\r\n {{ (option | customFieldLabel) || option.label || option.value }}\r\n </option>\r\n</select>\r\n",
8989
+ selector: 'vdr-facet-value-form-input',
8990
+ template: "<vdr-facet-value-selector\r\n *ngIf=\"facets$ | async as facets\"\r\n [readonly]=\"readonly\"\r\n [facets]=\"facets\"\r\n [formControl]=\"formControl\"\r\n></vdr-facet-value-selector>\r\n",
8792
8991
  changeDetection: ChangeDetectionStrategy.OnPush,
8793
- styles: ["select{width:100%}\n"]
8992
+ styles: [""]
8794
8993
  },] }
8795
8994
  ];
8796
- SelectFormInputComponent.propDecorators = {
8797
- readonly: [{ type: Input }]
8798
- };
8995
+ FacetValueFormInputComponent.ctorParameters = () => [
8996
+ { type: DataService }
8997
+ ];
8799
8998
 
8800
8999
  /**
8801
9000
  * @description
8802
- * Uses a regular text form input. This is the default input for `string` and `localeString` type fields.
9001
+ * Displays a number input. Default input for `int` and `float` type fields.
8803
9002
  *
8804
9003
  * @docsCategory custom-input-components
8805
9004
  * @docsPage default-inputs
8806
9005
  */
8807
- class TextFormInputComponent {
9006
+ class NumberFormInputComponent {
8808
9007
  get prefix() {
8809
9008
  var _a;
8810
9009
  return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.prefix) || this.config.prefix;
@@ -8813,1512 +9012,1672 @@ class TextFormInputComponent {
8813
9012
  var _a;
8814
9013
  return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.suffix) || this.config.suffix;
8815
9014
  }
9015
+ get min() {
9016
+ var _a;
9017
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.min) || this.config.min;
9018
+ }
9019
+ get max() {
9020
+ var _a;
9021
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.max) || this.config.max;
9022
+ }
9023
+ get step() {
9024
+ var _a;
9025
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.step) || this.config.step;
9026
+ }
8816
9027
  }
8817
- TextFormInputComponent.id = 'text-form-input';
8818
- TextFormInputComponent.decorators = [
9028
+ NumberFormInputComponent.id = 'number-form-input';
9029
+ NumberFormInputComponent.decorators = [
8819
9030
  { type: Component, args: [{
8820
- selector: 'vdr-text-form-input',
8821
- template: "<vdr-affixed-input\r\n [suffix]=\"suffix\"\r\n [prefix]=\"prefix\"\r\n>\r\n <input type=\"text\" [readonly]=\"readonly\" [formControl]=\"formControl\" />\r\n</vdr-affixed-input>\r\n",
9031
+ selector: 'vdr-number-form-input',
9032
+ template: "<vdr-affixed-input\r\n [suffix]=\"suffix\"\r\n [prefix]=\"prefix\"\r\n>\r\n <input\r\n type=\"number\"\r\n [readonly]=\"readonly\"\r\n [min]=\"min\"\r\n [max]=\"max\"\r\n [step]=\"step\"\r\n [formControl]=\"formControl\"\r\n />\r\n</vdr-affixed-input>\r\n",
8822
9033
  changeDetection: ChangeDetectionStrategy.OnPush,
8823
- styles: ["input{width:100%}\n"]
9034
+ styles: [""]
8824
9035
  },] }
8825
- ];
9036
+ ];
9037
+ NumberFormInputComponent.propDecorators = {
9038
+ readonly: [{ type: Input }]
9039
+ };
8826
9040
 
8827
9041
  /**
8828
9042
  * @description
8829
- * Uses textarea form input. This is the default input for `text` type fields.
9043
+ * Displays a password text input. Should be used with `string` type fields.
8830
9044
  *
8831
9045
  * @docsCategory custom-input-components
8832
9046
  * @docsPage default-inputs
8833
9047
  */
8834
- class TextareaFormInputComponent {
8835
- get spellcheck() {
8836
- return this.config.spellcheck === true;
8837
- }
9048
+ class PasswordFormInputComponent {
8838
9049
  }
8839
- TextareaFormInputComponent.id = 'textarea-form-input';
8840
- TextareaFormInputComponent.decorators = [
9050
+ PasswordFormInputComponent.id = 'password-form-input';
9051
+ PasswordFormInputComponent.decorators = [
8841
9052
  { type: Component, args: [{
8842
- selector: 'vdr-textarea-form-input',
8843
- template: "<textarea [spellcheck]=\"spellcheck\" autocomplete=\"off\" autocorrect=\"off\"\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n></textarea>\r\n",
9053
+ selector: 'vdr-password-form-input',
9054
+ template: "<input\r\n type=\"password\"\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n/>\r\n",
8844
9055
  changeDetection: ChangeDetectionStrategy.OnPush,
8845
- styles: [":host textarea{resize:both;height:6rem;width:100%}\n"]
9056
+ styles: [""]
8846
9057
  },] }
8847
9058
  ];
8848
9059
 
8849
- const defaultFormInputs = [
8850
- BooleanFormInputComponent,
8851
- CurrencyFormInputComponent,
8852
- DateFormInputComponent,
8853
- FacetValueFormInputComponent,
8854
- NumberFormInputComponent,
8855
- SelectFormInputComponent,
8856
- TextFormInputComponent,
8857
- ProductSelectorFormInputComponent,
8858
- CustomerGroupFormInputComponent,
8859
- PasswordFormInputComponent,
8860
- RelationFormInputComponent,
8861
- TextareaFormInputComponent,
8862
- RichTextFormInputComponent,
8863
- JsonEditorFormInputComponent,
8864
- ];
8865
- /**
8866
- * @description
8867
- * Registers a custom FormInputComponent which can be used to control the argument inputs
8868
- * of a {@link ConfigurableOperationDef} (e.g. CollectionFilter, ShippingMethod etc) or for
8869
- * a custom field.
8870
- *
8871
- * @example
8872
- * ```TypeScript
8873
- * \@NgModule({
8874
- * imports: [SharedModule],
8875
- * declarations: [MyCustomFieldControl],
8876
- * providers: [
8877
- * registerFormInputComponent('my-custom-input', MyCustomFieldControl),
8878
- * ],
8879
- * })
8880
- * export class MyUiExtensionModule {}
8881
- * ```
8882
- *
8883
- * This input component can then be used in a custom field:
8884
- *
8885
- * @example
8886
- * ```TypeScript
8887
- * const config = {
8888
- * // ...
8889
- * customFields: {
8890
- * ProductVariant: [
8891
- * {
8892
- * name: 'rrp',
8893
- * type: 'int',
8894
- * ui: { component: 'my-custom-input' },
8895
- * },
8896
- * ]
8897
- * }
8898
- * }
8899
- * ```
8900
- *
8901
- * or with an argument of a {@link ConfigurableOperationDef}:
8902
- *
8903
- * @example
8904
- * ```TypeScript
8905
- * args: {
8906
- * rrp: { type: 'int', ui: { component: 'my-custom-input' } },
8907
- * }
8908
- * ```
8909
- *
8910
- * @docsCategory custom-input-components
8911
- */
8912
- function registerFormInputComponent(id, component) {
8913
- return {
8914
- provide: APP_INITIALIZER,
8915
- multi: true,
8916
- useFactory: (registry) => () => {
8917
- registry.registerInputComponent(id, component);
8918
- },
8919
- deps: [ComponentRegistryService],
8920
- };
8921
- }
8922
9060
  /**
8923
9061
  * @description
8924
- * **Deprecated** use `registerFormInputComponent()` in combination with the customField `ui` config instead.
8925
- *
8926
- * Registers a custom component to act as the form input control for the given custom field.
8927
- * This should be used in the NgModule `providers` array of your ui extension module.
8928
- *
8929
- * @example
8930
- * ```TypeScript
8931
- * \@NgModule({
8932
- * imports: [SharedModule],
8933
- * declarations: [MyCustomFieldControl],
8934
- * providers: [
8935
- * registerCustomFieldComponent('Product', 'someCustomField', MyCustomFieldControl),
8936
- * ],
8937
- * })
8938
- * export class MyUiExtensionModule {}
8939
- * ```
8940
- *
8941
- * @deprecated use `registerFormInputComponent()` in combination with the customField `ui` config instead.
8942
- *
8943
- * @docsCategory custom-input-components
8944
- */
8945
- function registerCustomFieldComponent(entity, fieldName, component) {
8946
- return {
8947
- provide: APP_INITIALIZER,
8948
- multi: true,
8949
- useFactory: (customFieldComponentService) => () => {
8950
- customFieldComponentService.registerCustomFieldComponent(entity, fieldName, component);
8951
- },
8952
- deps: [CustomFieldComponentService],
8953
- };
8954
- }
8955
- /**
8956
- * Registers the default form input components.
9062
+ * A helper class used to manage selection of list items. Supports multiple selection via
9063
+ * cmd/ctrl/shift key.
8957
9064
  */
8958
- function registerDefaultFormInputs() {
8959
- return defaultFormInputs.map(cmp => registerFormInputComponent(cmp.id, cmp));
9065
+ class SelectionManager {
9066
+ constructor(options) {
9067
+ this.options = options;
9068
+ this._selection = [];
9069
+ this.items = [];
9070
+ }
9071
+ get selection() {
9072
+ return this._selection;
9073
+ }
9074
+ setMultiSelect(isMultiSelect) {
9075
+ this.options.multiSelect = isMultiSelect;
9076
+ }
9077
+ setCurrentItems(items) {
9078
+ this.items = items;
9079
+ }
9080
+ toggleSelection(item, event) {
9081
+ const { multiSelect, itemsAreEqual, additiveMode } = this.options;
9082
+ const index = this._selection.findIndex(a => itemsAreEqual(a, item));
9083
+ if (multiSelect && (event === null || event === void 0 ? void 0 : event.shiftKey) && 1 <= this._selection.length) {
9084
+ const lastSelection = this._selection[this._selection.length - 1];
9085
+ const lastSelectionIndex = this.items.findIndex(a => itemsAreEqual(a, lastSelection));
9086
+ const currentIndex = this.items.findIndex(a => itemsAreEqual(a, item));
9087
+ const start = currentIndex < lastSelectionIndex ? currentIndex : lastSelectionIndex;
9088
+ const end = currentIndex > lastSelectionIndex ? currentIndex + 1 : lastSelectionIndex;
9089
+ this._selection.push(...this.items.slice(start, end).filter(a => !this._selection.find(s => itemsAreEqual(a, s))));
9090
+ }
9091
+ else if (index === -1) {
9092
+ if (multiSelect && ((event === null || event === void 0 ? void 0 : event.ctrlKey) || (event === null || event === void 0 ? void 0 : event.shiftKey) || additiveMode)) {
9093
+ this._selection.push(item);
9094
+ }
9095
+ else {
9096
+ this._selection = [item];
9097
+ }
9098
+ }
9099
+ else {
9100
+ if (multiSelect && (event === null || event === void 0 ? void 0 : event.ctrlKey)) {
9101
+ this._selection.splice(index, 1);
9102
+ }
9103
+ else if (1 < this._selection.length && !additiveMode) {
9104
+ this._selection = [item];
9105
+ }
9106
+ else {
9107
+ this._selection.splice(index, 1);
9108
+ }
9109
+ }
9110
+ // Make the selection mutable
9111
+ this._selection = this._selection.map(x => (Object.assign({}, x)));
9112
+ }
9113
+ selectMultiple(items) {
9114
+ this._selection = items;
9115
+ }
9116
+ isSelected(item) {
9117
+ return !!this._selection.find(a => this.options.itemsAreEqual(a, item));
9118
+ }
9119
+ lastSelected() {
9120
+ return this._selection[this._selection.length - 1];
9121
+ }
8960
9122
  }
8961
9123
 
8962
- class ActionBarItemsComponent {
8963
- constructor(navBuilderService, route, dataService, notificationService) {
8964
- this.navBuilderService = navBuilderService;
8965
- this.route = route;
9124
+ class ProductMultiSelectorDialogComponent {
9125
+ constructor(dataService, changeDetector) {
8966
9126
  this.dataService = dataService;
8967
- this.notificationService = notificationService;
8968
- this.locationId$ = new BehaviorSubject('');
9127
+ this.changeDetector = changeDetector;
9128
+ this.mode = 'product';
9129
+ this.initialSelectionIds = [];
9130
+ this.searchTerm$ = new BehaviorSubject('');
9131
+ this.searchFacetValueIds$ = new BehaviorSubject([]);
9132
+ this.paginationConfig = {
9133
+ currentPage: 1,
9134
+ itemsPerPage: 25,
9135
+ totalItems: 1,
9136
+ };
9137
+ this.paginationConfig$ = new BehaviorSubject(this.paginationConfig);
8969
9138
  }
8970
9139
  ngOnInit() {
8971
- this.items$ = combineLatest(this.navBuilderService.actionBarConfig$, this.locationId$).pipe(map(([items, locationId]) => items.filter(config => config.locationId === locationId)));
8972
- }
8973
- ngOnChanges(changes) {
8974
- if ('locationId' in changes) {
8975
- this.locationId$.next(changes['locationId'].currentValue);
8976
- }
8977
- }
8978
- handleClick(event, item) {
8979
- if (typeof item.onClick === 'function') {
8980
- item.onClick(event, {
8981
- route: this.route,
8982
- dataService: this.dataService,
8983
- notificationService: this.notificationService,
9140
+ const idFn = this.mode === 'product'
9141
+ ? (a, b) => a.productId === b.productId
9142
+ : (a, b) => a.productVariantId === b.productVariantId;
9143
+ this.selectionManager = new SelectionManager({
9144
+ multiSelect: true,
9145
+ itemsAreEqual: idFn,
9146
+ additiveMode: true,
9147
+ });
9148
+ const searchQueryResult = this.dataService.product.searchProducts('', this.paginationConfig.itemsPerPage, 0);
9149
+ const result$ = combineLatest(this.searchTerm$, this.searchFacetValueIds$, this.paginationConfig$).subscribe(([term, facetValueIds, pagination]) => {
9150
+ const take = +pagination.itemsPerPage;
9151
+ const skip = (pagination.currentPage - 1) * take;
9152
+ return searchQueryResult.ref.refetch({
9153
+ input: { skip, take, term, facetValueIds, groupByProduct: this.mode === 'product' },
8984
9154
  });
9155
+ });
9156
+ this.items$ = searchQueryResult.stream$.pipe(tap(data => {
9157
+ this.paginationConfig.totalItems = data.search.totalItems;
9158
+ this.selectionManager.setCurrentItems(data.search.items);
9159
+ }), map(data => data.search.items));
9160
+ this.facetValues$ = searchQueryResult.stream$.pipe(map(data => data.search.facetValues));
9161
+ if (this.initialSelectionIds.length) {
9162
+ if (this.mode === 'product') {
9163
+ this.dataService.product
9164
+ .getProducts({
9165
+ filter: {
9166
+ id: {
9167
+ in: this.initialSelectionIds,
9168
+ },
9169
+ },
9170
+ })
9171
+ .single$.subscribe(({ products }) => {
9172
+ this.selectionManager.selectMultiple(products.items.map(product => ({
9173
+ productId: product.id,
9174
+ productName: product.name,
9175
+ })));
9176
+ this.changeDetector.markForCheck();
9177
+ });
9178
+ }
9179
+ else {
9180
+ this.dataService.product
9181
+ .getProductVariants({
9182
+ filter: {
9183
+ id: {
9184
+ in: this.initialSelectionIds,
9185
+ },
9186
+ },
9187
+ })
9188
+ .single$.subscribe(({ productVariants }) => {
9189
+ this.selectionManager.selectMultiple(productVariants.items.map(variant => ({
9190
+ productVariantId: variant.id,
9191
+ productVariantName: variant.name,
9192
+ })));
9193
+ this.changeDetector.markForCheck();
9194
+ });
9195
+ }
8985
9196
  }
8986
9197
  }
8987
- getRouterLink(item) {
8988
- return this.navBuilderService.getRouterLink(item, this.route);
9198
+ trackByFn(index, item) {
9199
+ return item.productId;
8989
9200
  }
8990
- getButtonStyles(item) {
8991
- const styles = ['btn'];
8992
- if (item.buttonStyle && item.buttonStyle === 'link') {
8993
- styles.push('btn-link');
8994
- return styles;
8995
- }
8996
- styles.push(this.getButtonColorClass(item));
8997
- return styles;
9201
+ setSearchTerm(term) {
9202
+ this.searchTerm$.next(term);
8998
9203
  }
8999
- getButtonColorClass(item) {
9000
- switch (item.buttonColor) {
9001
- case undefined:
9002
- case 'primary':
9003
- return item.buttonStyle === 'outline' ? 'btn-outline' : 'btn-primary';
9004
- case 'success':
9005
- return item.buttonStyle === 'outline' ? 'btn-success-outline' : 'btn-success';
9006
- case 'warning':
9007
- return item.buttonStyle === 'outline' ? 'btn-warning-outline' : 'btn-warning';
9008
- default:
9009
- assertNever(item.buttonColor);
9010
- return '';
9011
- }
9204
+ setFacetValueIds(ids) {
9205
+ this.searchFacetValueIds$.next(ids);
9206
+ }
9207
+ toggleSelection(item, event) {
9208
+ this.selectionManager.toggleSelection(item, event);
9209
+ }
9210
+ clearSelection() {
9211
+ this.selectionManager.selectMultiple([]);
9212
+ }
9213
+ isSelected(item) {
9214
+ return this.selectionManager.isSelected(item);
9215
+ }
9216
+ entityInfoClick(event) {
9217
+ event.preventDefault();
9218
+ event.stopPropagation();
9219
+ }
9220
+ pageChange(page) {
9221
+ this.paginationConfig.currentPage = page;
9222
+ this.paginationConfig$.next(this.paginationConfig);
9223
+ }
9224
+ itemsPerPageChange(itemsPerPage) {
9225
+ this.paginationConfig.itemsPerPage = itemsPerPage;
9226
+ this.paginationConfig$.next(this.paginationConfig);
9227
+ }
9228
+ select() {
9229
+ this.resolveWith(this.selectionManager.selection);
9230
+ }
9231
+ cancel() {
9232
+ this.resolveWith();
9012
9233
  }
9013
9234
  }
9014
- ActionBarItemsComponent.decorators = [
9235
+ ProductMultiSelectorDialogComponent.decorators = [
9015
9236
  { type: Component, args: [{
9016
- selector: 'vdr-action-bar-items',
9017
- template: "<vdr-ui-extension-point [locationId]=\"locationId\" api=\"actionBar\" [leftPx]=\"-24\" [topPx]=\"-6\">\r\n <ng-container *ngFor=\"let item of items$ | async\">\r\n <button\r\n *vdrIfPermissions=\"item.requiresPermission\"\r\n [routerLink]=\"getRouterLink(item)\"\r\n [disabled]=\"item.disabled ? (item.disabled | async) : false\"\r\n (click)=\"handleClick($event, item)\"\r\n [ngClass]=\"getButtonStyles(item)\"\r\n >\r\n <clr-icon *ngIf=\"item.icon\" [attr.shape]=\"item.icon\"></clr-icon>\r\n {{ item.label | translate }}\r\n </button>\r\n </ng-container>\r\n</vdr-ui-extension-point>\r\n",
9237
+ selector: 'vdr-product-multi-selector-dialog',
9238
+ template: "<ng-template vdrDialogTitle>\r\n <div class=\"title-row\">\r\n <span *ngIf=\"mode === 'product'\">{{ 'common.select-products' | translate }}</span>\r\n <span *ngIf=\"mode === 'variant'\">{{ 'common.select-variants' | translate }}</span>\r\n </div>\r\n</ng-template>\r\n<vdr-product-search-input\r\n #productSearchInputComponent\r\n [facetValueResults]=\"facetValues$ | async\"\r\n (searchTermChange)=\"setSearchTerm($event)\"\r\n (facetValueChange)=\"setFacetValueIds($event)\"\r\n></vdr-product-search-input>\r\n<div class=\"flex-wrapper\">\r\n <div class=\"gallery\">\r\n <div\r\n class=\"card\"\r\n *ngFor=\"let item of (items$ | async) || [] | paginate: paginationConfig; trackBy: trackByFn\"\r\n (click)=\"toggleSelection(item, $event)\"\r\n [class.selected]=\"isSelected(item)\"\r\n >\r\n <div class=\"card-img\">\r\n <vdr-select-toggle\r\n [selected]=\"isSelected(item)\"\r\n [disabled]=\"true\"\r\n [hiddenWhenOff]=\"true\"\r\n ></vdr-select-toggle>\r\n <img\r\n [src]=\"\r\n (mode === 'product'\r\n ? item.productAsset\r\n : item.productVariantAsset || item.productAsset\r\n ) | assetPreview: 'thumb'\r\n \"\r\n />\r\n </div>\r\n <div class=\"detail\">\r\n <span [title]=\"mode === 'product' ? item.productName : item.productVariantName\">{{\r\n mode === 'product' ? item.productName : item.productVariantName\r\n }}</span>\r\n <div *ngIf=\"mode === 'variant'\"><small>{{ item.sku }}</small></div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"selection\">\r\n <div class=\"m2 flex center\">\r\n <div>\r\n {{ 'common.items-selected-count' | translate: { count: selectionManager.selection.length } }}\r\n </div>\r\n <div class=\"flex-spacer\"></div>\r\n <button class=\"btn btn-sm btn-link\" (click)=\"clearSelection()\">\r\n <cds-icon shape=\"times\"></cds-icon> {{ 'common.clear-selection' | translate }}\r\n </button>\r\n </div>\r\n <div class=\"selected-items\">\r\n <div *ngFor=\"let item of selectionManager.selection\" class=\"flex item-row\">\r\n <div class=\"\">{{ mode === 'product' ? item.productName : item.productVariantName }}</div>\r\n <div class=\"flex-spacer\"></div>\r\n <div>\r\n <button class=\"icon-button\" (click)=\"toggleSelection(item, $event)\">\r\n <cds-icon shape=\"times\"></cds-icon>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div class=\"paging-controls\">\r\n <vdr-items-per-page-controls\r\n [itemsPerPage]=\"paginationConfig.itemsPerPage\"\r\n (itemsPerPageChange)=\"itemsPerPageChange($event)\"\r\n ></vdr-items-per-page-controls>\r\n\r\n <vdr-pagination-controls\r\n [currentPage]=\"paginationConfig.currentPage\"\r\n [itemsPerPage]=\"paginationConfig.itemsPerPage\"\r\n [totalItems]=\"paginationConfig.totalItems\"\r\n (pageChange)=\"pageChange($event)\"\r\n ></vdr-pagination-controls>\r\n</div>\r\n\r\n<ng-template vdrDialogButtons>\r\n <button type=\"button\" class=\"btn\" (click)=\"cancel()\">{{ 'common.cancel' | translate }}</button>\r\n <button\r\n type=\"submit\"\r\n (click)=\"select()\"\r\n class=\"btn btn-primary\"\r\n [disabled]=\"selectionManager.selection.length === 0\"\r\n >\r\n {{ 'common.select-items-with-count' | translate: { count: selectionManager.selection.length } }}\r\n </button>\r\n</ng-template>\r\n",
9018
9239
  changeDetection: ChangeDetectionStrategy.OnPush,
9019
- styles: [":host{display:inline-block;min-height:36px}\n"]
9240
+ styles: [":host{display:flex;flex-direction:column;flex-direction:1;height:70vh}.flex-wrapper{display:flex;overflow-y:hidden}.gallery{flex:1;display:grid;grid-template-columns:repeat(auto-fill,125px);grid-template-rows:repeat(auto-fill,200px);grid-gap:10px 20px;padding-left:12px;padding-top:12px;padding-bottom:64px;overflow-y:auto}.gallery .card:hover{box-shadow:0 .125rem 0 0 var(--color-primary-500);border:1px solid var(--color-primary-500)}.detail{margin:0 3px;font-size:12px;line-height:.8rem}vdr-select-toggle{position:absolute;top:-12px;left:-12px}vdr-select-toggle ::ng-deep .toggle{box-shadow:0 5px 5px -4px #000000bf}.card.selected{box-shadow:0 .125rem 0 0 var(--color-primary-500);border:1px solid var(--color-primary-500)}.card.selected .selected-checkbox{opacity:1}.selection{width:23vw;max-width:400px;padding:6px;display:flex;flex-direction:column}.selection .selected-items{flex:1;overflow-y:auto}.selection .selected-items .item-row{padding-left:3px}.selection .selected-items .item-row:hover{background-color:var(--color-component-bg-200)}.paging-controls{display:flex;align-items:center;justify-content:space-between}\n"]
9020
9241
  },] }
9021
9242
  ];
9022
- ActionBarItemsComponent.ctorParameters = () => [
9023
- { type: NavBuilderService },
9024
- { type: ActivatedRoute },
9243
+ ProductMultiSelectorDialogComponent.ctorParameters = () => [
9025
9244
  { type: DataService },
9026
- { type: NotificationService }
9027
- ];
9028
- ActionBarItemsComponent.propDecorators = {
9029
- locationId: [{ type: HostBinding, args: ['attr.data-location-id',] }, { type: Input }]
9030
- };
9245
+ { type: ChangeDetectorRef }
9246
+ ];
9031
9247
 
9032
- class ActionBarLeftComponent {
9033
- constructor() {
9034
- this.grow = false;
9248
+ class ProductMultiSelectorFormInputComponent {
9249
+ constructor(modalService, dataService, changeDetector) {
9250
+ this.modalService = modalService;
9251
+ this.dataService = dataService;
9252
+ this.changeDetector = changeDetector;
9253
+ this.mode = 'product';
9254
+ this.isListInput = true;
9035
9255
  }
9036
- }
9037
- ActionBarLeftComponent.decorators = [
9038
- { type: Component, args: [{
9039
- selector: 'vdr-ab-left',
9040
- template: `
9041
- <ng-content></ng-content>
9042
- `
9043
- },] }
9044
- ];
9045
- ActionBarLeftComponent.propDecorators = {
9046
- grow: [{ type: Input }]
9047
- };
9048
- class ActionBarRightComponent {
9049
- constructor() {
9050
- this.grow = false;
9256
+ ngOnInit() {
9257
+ var _a, _b;
9258
+ this.mode = (_b = (_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.selectionMode) !== null && _b !== void 0 ? _b : 'product';
9259
+ }
9260
+ select() {
9261
+ this.modalService
9262
+ .fromComponent(ProductMultiSelectorDialogComponent, {
9263
+ size: 'xl',
9264
+ locals: {
9265
+ mode: this.mode,
9266
+ initialSelectionIds: this.formControl.value,
9267
+ },
9268
+ })
9269
+ .subscribe(selection => {
9270
+ if (selection) {
9271
+ this.formControl.setValue(selection.map(item => this.mode === 'product' ? item.productId : item.productVariantId));
9272
+ this.changeDetector.markForCheck();
9273
+ }
9274
+ });
9051
9275
  }
9052
9276
  }
9053
- ActionBarRightComponent.decorators = [
9277
+ ProductMultiSelectorFormInputComponent.id = 'product-multi-form-input';
9278
+ ProductMultiSelectorFormInputComponent.decorators = [
9054
9279
  { type: Component, args: [{
9055
- selector: 'vdr-ab-right',
9056
- template: `
9057
- <ng-content></ng-content>
9058
- `
9280
+ selector: 'vdr-product-multi-selector-form-input',
9281
+ template: "<div class=\"flex\">\r\n <button (click)=\"select()\" class=\"btn btn-sm btn-secondary\">\r\n {{ 'common.items-selected-count' | translate: { count: formControl.value?.length ?? 0 } }}...\r\n </button>\r\n</div>\r\n",
9282
+ changeDetection: ChangeDetectionStrategy.OnPush,
9283
+ styles: [""]
9059
9284
  },] }
9060
9285
  ];
9061
- ActionBarRightComponent.propDecorators = {
9062
- grow: [{ type: Input }]
9063
- };
9064
- class ActionBarComponent {
9286
+ ProductMultiSelectorFormInputComponent.ctorParameters = () => [
9287
+ { type: ModalService },
9288
+ { type: DataService },
9289
+ { type: ChangeDetectorRef }
9290
+ ];
9291
+ ProductMultiSelectorFormInputComponent.propDecorators = {
9292
+ config: [{ type: Input }],
9293
+ formControl: [{ type: Input }],
9294
+ readonly: [{ type: Input }]
9295
+ };
9296
+
9297
+ /**
9298
+ * @description
9299
+ * Allows the selection of multiple ProductVariants via an autocomplete select input.
9300
+ * Should be used with `ID` type **list** fields which represent ProductVariant IDs.
9301
+ *
9302
+ * @docsCategory custom-input-components
9303
+ * @docsPage default-inputs
9304
+ */
9305
+ class ProductSelectorFormInputComponent {
9306
+ constructor(dataService) {
9307
+ this.dataService = dataService;
9308
+ this.isListInput = true;
9309
+ }
9310
+ ngOnInit() {
9311
+ this.formControl.setValidators([
9312
+ control => {
9313
+ if (!control.value || !control.value.length) {
9314
+ return {
9315
+ atLeastOne: { length: control.value.length },
9316
+ };
9317
+ }
9318
+ return null;
9319
+ },
9320
+ ]);
9321
+ this.selection$ = this.formControl.valueChanges.pipe(startWith(this.formControl.value), switchMap(value => {
9322
+ if (Array.isArray(value) && 0 < value.length) {
9323
+ return forkJoin(value.map(id => this.dataService.product
9324
+ .getProductVariant(id)
9325
+ .mapSingle(data => data.productVariant)));
9326
+ }
9327
+ return of([]);
9328
+ }), map(variants => variants.filter(notNullOrUndefined)));
9329
+ }
9330
+ addProductVariant(product) {
9331
+ const value = this.formControl.value;
9332
+ this.formControl.setValue([...new Set([...value, product.productVariantId])]);
9333
+ }
9334
+ removeProductVariant(id) {
9335
+ const value = this.formControl.value;
9336
+ this.formControl.setValue(value.filter(_id => _id !== id));
9337
+ }
9065
9338
  }
9066
- ActionBarComponent.decorators = [
9339
+ ProductSelectorFormInputComponent.id = 'product-selector-form-input';
9340
+ ProductSelectorFormInputComponent.decorators = [
9067
9341
  { type: Component, args: [{
9068
- selector: 'vdr-action-bar',
9069
- template: "<div class=\"left-content\" [class.grow]=\"left?.grow\"><ng-content select=\"vdr-ab-left\"></ng-content></div>\r\n<div class=\"right-content\" [class.grow]=\"right?.grow\"><ng-content select=\"vdr-ab-right\"></ng-content></div>\r\n",
9070
- styles: [":host{display:flex;justify-content:space-between;align-items:baseline;background-color:var(--color-component-bg-100);position:sticky;top:-24px;z-index:25;border-bottom:1px solid var(--color-component-border-200)}:host>.grow{flex:1}\n"]
9342
+ selector: 'vdr-product-selector-form-input',
9343
+ template: "<ul class=\"list-unstyled\">\r\n <li *ngFor=\"let variant of selection$ | async\" class=\"variant\">\r\n <div class=\"thumb\">\r\n <img [src]=\"variant.product.featuredAsset | assetPreview: 32\" />\r\n </div>\r\n <div class=\"detail\">\r\n <div>{{ variant.name }}</div>\r\n <div class=\"sku\">{{ variant.sku }}</div>\r\n </div>\r\n <div class=\"flex-spacer\"></div>\r\n <button\r\n class=\"btn btn-link btn-sm btn-warning\"\r\n (click)=\"removeProductVariant(variant.id)\"\r\n [title]=\"'common.remove-item-from-list' | translate\"\r\n >\r\n <clr-icon shape=\"times\"></clr-icon>\r\n </button>\r\n </li>\r\n</ul>\r\n<vdr-product-selector (productSelected)=\"addProductVariant($event)\"></vdr-product-selector>\r\n",
9344
+ changeDetection: ChangeDetectionStrategy.OnPush,
9345
+ styles: [".variant{margin-bottom:6px;display:flex;align-items:center;transition:background-color .2s}.variant:hover{background-color:var(--color-component-bg-200)}.thumb{margin-right:6px}.sku{color:var(--color-grey-400);font-size:smaller;line-height:1em}\n"]
9071
9346
  },] }
9072
9347
  ];
9073
- ActionBarComponent.propDecorators = {
9074
- left: [{ type: ContentChild, args: [ActionBarLeftComponent,] }],
9075
- right: [{ type: ContentChild, args: [ActionBarRightComponent,] }]
9076
- };
9348
+ ProductSelectorFormInputComponent.ctorParameters = () => [
9349
+ { type: DataService }
9350
+ ];
9077
9351
 
9078
- class AddressFormComponent {
9352
+ /**
9353
+ * @description
9354
+ * The default input component for `relation` type custom fields. Allows the selection
9355
+ * of a ProductVariant, Product, Customer or Asset. For other entity types, a custom
9356
+ * implementation will need to be defined. See {@link registerFormInputComponent}.
9357
+ *
9358
+ * @docsCategory custom-input-components
9359
+ * @docsPage default-inputs
9360
+ */
9361
+ class RelationFormInputComponent {
9079
9362
  }
9080
- AddressFormComponent.decorators = [
9363
+ RelationFormInputComponent.id = 'relation-form-input';
9364
+ RelationFormInputComponent.decorators = [
9081
9365
  { type: Component, args: [{
9082
- selector: 'vdr-address-form',
9083
- template: "<form [formGroup]=\"formGroup\">\r\n <clr-input-container>\r\n <label>{{ 'customer.full-name' | translate }}</label>\r\n <input formControlName=\"fullName\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n\r\n <div class=\"clr-row\">\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.street-line-1' | translate }}</label>\r\n <input formControlName=\"streetLine1\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.street-line-2' | translate }}</label>\r\n <input formControlName=\"streetLine2\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n </div>\r\n <div class=\"clr-row\">\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.city' | translate }}</label>\r\n <input formControlName=\"city\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.province' | translate }}</label>\r\n <input formControlName=\"province\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n </div>\r\n <div class=\"clr-row\">\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.postal-code' | translate }}</label>\r\n <input formControlName=\"postalCode\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.country' | translate }}</label>\r\n <select name=\"countryCode\" formControlName=\"countryCode\" clrInput clrSelect>\r\n <option *ngFor=\"let country of availableCountries\" [value]=\"country.code\">\r\n {{ country.name }}\r\n </option>\r\n </select>\r\n </clr-input-container>\r\n </div>\r\n </div>\r\n <clr-input-container>\r\n <label>{{ 'customer.phone-number' | translate }}</label>\r\n <input formControlName=\"phoneNumber\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n <section formGroupName=\"customFields\" *ngIf=\"formGroup.get('customFields') as customFieldsGroup\">\r\n <label>{{ 'common.custom-fields' | translate }}</label>\r\n <vdr-tabbed-custom-fields\r\n entityName=\"Address\"\r\n [customFields]=\"customFields\"\r\n [customFieldsFormGroup]=\"customFieldsGroup\"\r\n ></vdr-tabbed-custom-fields>\r\n </section>\r\n</form>\r\n",
9366
+ selector: 'vdr-relation-form-input',
9367
+ template: "<div [ngSwitch]=\"config.entity\">\r\n <vdr-relation-asset-input\r\n *ngSwitchCase=\"'Asset'\"\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-asset-input>\r\n <vdr-relation-product-input\r\n *ngSwitchCase=\"'Product'\"\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-product-input>\r\n <vdr-relation-customer-input\r\n *ngSwitchCase=\"'Customer'\"\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-customer-input>\r\n <vdr-relation-product-variant-input\r\n *ngSwitchCase=\"'ProductVariant'\"\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-product-variant-input>\r\n <ng-template ngSwitchDefault>\r\n <vdr-relation-generic-input\r\n [parentFormControl]=\"formControl\"\r\n [config]=\"config\"\r\n [readonly]=\"readonly\"\r\n ></vdr-relation-generic-input>\r\n </ng-template>\r\n</div>\r\n",
9084
9368
  changeDetection: ChangeDetectionStrategy.OnPush,
9085
- styles: [""]
9369
+ styles: [":host{display:block;background-color:var(--color-component-bg-200);padding:3px}\n"]
9086
9370
  },] }
9087
9371
  ];
9088
- AddressFormComponent.propDecorators = {
9089
- customFields: [{ type: Input }],
9090
- formGroup: [{ type: Input }],
9091
- availableCountries: [{ type: Input }]
9372
+ RelationFormInputComponent.propDecorators = {
9373
+ readonly: [{ type: Input }]
9092
9374
  };
9093
9375
 
9094
9376
  /**
9095
- * A wrapper around an <input> element which adds a prefix and/or a suffix element.
9377
+ * @description
9378
+ * Uses the {@link RichTextEditorComponent} as in input for `text` type fields.
9379
+ *
9380
+ * @docsCategory custom-input-components
9381
+ * @docsPage default-inputs
9096
9382
  */
9097
- class AffixedInputComponent {
9383
+ class RichTextFormInputComponent {
9098
9384
  }
9099
- AffixedInputComponent.decorators = [
9385
+ RichTextFormInputComponent.id = 'rich-text-form-input';
9386
+ RichTextFormInputComponent.decorators = [
9100
9387
  { type: Component, args: [{
9101
- selector: 'vdr-affixed-input',
9102
- template: "<div [class.has-prefix]=\"!!prefix\" [class.has-suffix]=\"!!suffix\">\r\n <ng-content></ng-content>\r\n</div>\r\n<div class=\"affix prefix\" *ngIf=\"prefix\">{{ prefix }}</div>\r\n<div class=\"affix suffix\" *ngIf=\"suffix\">{{ suffix }}</div>\r\n",
9388
+ selector: 'vdr-rich-text-form-input',
9389
+ template: "<vdr-rich-text-editor\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n></vdr-rich-text-editor>\r\n",
9103
9390
  changeDetection: ChangeDetectionStrategy.OnPush,
9104
- styles: [":host{display:inline-flex}.affix{color:var(--color-grey-800);display:flex;align-items:center;background-color:var(--color-grey-200);border:1px solid var(--color-component-border-300);top:1px;padding:3px;line-height:.58333rem;transition:border .2s}::ng-deep .has-prefix input{border-top-left-radius:0!important;border-bottom-left-radius:0!important}.prefix{order:-1;border-radius:3px 0 0 3px;border-right:none}::ng-deep .has-suffix input{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.suffix{border-radius:0 3px 3px 0;border-left:none}\n"]
9391
+ styles: [":host textarea{resize:both;height:6rem;width:100%}\n"]
9105
9392
  },] }
9106
- ];
9107
- AffixedInputComponent.propDecorators = {
9108
- prefix: [{ type: Input }],
9109
- suffix: [{ type: Input }]
9110
- };
9393
+ ];
9111
9394
 
9112
9395
  /**
9113
- * A form input control which displays a number input with a percentage sign suffix.
9396
+ * @description
9397
+ * Uses a select input to allow the selection of a string value. Should be used with
9398
+ * `string` type fields with options.
9399
+ *
9400
+ * @docsCategory custom-input-components
9401
+ * @docsPage default-inputs
9114
9402
  */
9115
- class PercentageSuffixInputComponent {
9116
- constructor() {
9117
- this.disabled = false;
9118
- this.readonly = false;
9119
- }
9120
- ngOnChanges(changes) {
9121
- if ('value' in changes) {
9122
- this.writeValue(changes['value'].currentValue);
9123
- }
9124
- }
9125
- registerOnChange(fn) {
9126
- this.onChange = fn;
9127
- }
9128
- registerOnTouched(fn) {
9129
- this.onTouch = fn;
9130
- }
9131
- setDisabledState(isDisabled) {
9132
- this.disabled = isDisabled;
9133
- }
9134
- onInput(value) {
9135
- this.onChange(value);
9136
- }
9137
- writeValue(value) {
9138
- const numericValue = +value;
9139
- if (!Number.isNaN(numericValue)) {
9140
- this._value = numericValue;
9141
- }
9403
+ class SelectFormInputComponent {
9404
+ get options() {
9405
+ var _a;
9406
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.options) || this.config.options;
9142
9407
  }
9143
9408
  }
9144
- PercentageSuffixInputComponent.decorators = [
9409
+ SelectFormInputComponent.id = 'select-form-input';
9410
+ SelectFormInputComponent.decorators = [
9145
9411
  { type: Component, args: [{
9146
- selector: 'vdr-percentage-suffix-input',
9147
- template: `
9148
- <vdr-affixed-input suffix="%">
9149
- <input
9150
- type="number"
9151
- step="1"
9152
- [value]="_value"
9153
- [disabled]="disabled"
9154
- [readonly]="readonly"
9155
- (input)="onInput($event.target.value)"
9156
- (focus)="onTouch()"
9157
- />
9158
- </vdr-affixed-input>
9159
- `,
9160
- providers: [
9161
- {
9162
- provide: NG_VALUE_ACCESSOR,
9163
- useExisting: PercentageSuffixInputComponent,
9164
- multi: true,
9165
- },
9166
- ],
9167
- styles: [`
9168
- :host {
9169
- padding: 0;
9170
- }
9171
- `]
9412
+ selector: 'vdr-select-form-input',
9413
+ template: "<select clrSelect [formControl]=\"formControl\" [vdrDisabled]=\"readonly\">\r\n <option *ngIf=\"config.nullable\" [ngValue]=\"null\"></option>\r\n <option *ngFor=\"let option of options\" [ngValue]=\"option.value\">\r\n {{ (option | customFieldLabel) || option.label || option.value }}\r\n </option>\r\n</select>\r\n",
9414
+ changeDetection: ChangeDetectionStrategy.OnPush,
9415
+ styles: ["select{width:100%}\n"]
9172
9416
  },] }
9173
9417
  ];
9174
- PercentageSuffixInputComponent.propDecorators = {
9175
- disabled: [{ type: Input }],
9176
- readonly: [{ type: Input }],
9177
- value: [{ type: Input }]
9418
+ SelectFormInputComponent.propDecorators = {
9419
+ readonly: [{ type: Input }]
9178
9420
  };
9179
9421
 
9180
9422
  /**
9181
- * A component for selecting files to upload as new Assets.
9423
+ * @description
9424
+ * Uses a regular text form input. This is the default input for `string` and `localeString` type fields.
9425
+ *
9426
+ * @docsCategory custom-input-components
9427
+ * @docsPage default-inputs
9182
9428
  */
9183
- class AssetFileInputComponent {
9184
- constructor(serverConfig) {
9185
- this.serverConfig = serverConfig;
9186
- /**
9187
- * CSS selector of the DOM element which will be masked by the file
9188
- * drop zone. Defaults to `body`.
9189
- */
9190
- this.dropZoneTarget = 'body';
9191
- this.uploading = false;
9192
- this.selectFiles = new EventEmitter();
9193
- this.dragging = false;
9194
- this.overDropZone = false;
9195
- this.dropZoneStyle = {
9196
- 'width.px': 0,
9197
- 'height.px': 0,
9198
- 'top.px': 0,
9199
- 'left.px': 0,
9200
- };
9201
- }
9202
- ngOnInit() {
9203
- this.accept = this.serverConfig.serverConfig.permittedAssetTypes.join(',');
9204
- this.fitDropZoneToTarget();
9205
- }
9206
- onDragEnter() {
9207
- this.dragging = true;
9208
- this.fitDropZoneToTarget();
9209
- }
9210
- // DragEvent is not supported in Safari, see https://github.com/vendure-ecommerce/vendure/pull/284
9211
- onDragLeave(event) {
9212
- if (!event.clientX && !event.clientY) {
9213
- this.dragging = false;
9214
- }
9215
- }
9216
- /**
9217
- * Preventing this event is required to make dropping work.
9218
- * See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API#Define_a_drop_zone
9219
- */
9220
- onDragOver(event) {
9221
- event.preventDefault();
9222
- }
9223
- // DragEvent is not supported in Safari, see https://github.com/vendure-ecommerce/vendure/pull/284
9224
- onDrop(event) {
9225
- event.preventDefault();
9226
- this.dragging = false;
9227
- this.overDropZone = false;
9228
- const files = Array.from(event.dataTransfer ? event.dataTransfer.items : [])
9229
- .map(i => i.getAsFile())
9230
- .filter(notNullOrUndefined);
9231
- this.selectFiles.emit(files);
9232
- }
9233
- select(event) {
9234
- const files = event.target.files;
9235
- if (files) {
9236
- this.selectFiles.emit(Array.from(files));
9237
- }
9429
+ class TextFormInputComponent {
9430
+ get prefix() {
9431
+ var _a;
9432
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.prefix) || this.config.prefix;
9238
9433
  }
9239
- fitDropZoneToTarget() {
9240
- const target = document.querySelector(this.dropZoneTarget);
9241
- if (target) {
9242
- const rect = target.getBoundingClientRect();
9243
- this.dropZoneStyle['width.px'] = rect.width;
9244
- this.dropZoneStyle['height.px'] = rect.height;
9245
- this.dropZoneStyle['top.px'] = rect.top;
9246
- this.dropZoneStyle['left.px'] = rect.left;
9247
- }
9434
+ get suffix() {
9435
+ var _a;
9436
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.suffix) || this.config.suffix;
9248
9437
  }
9249
9438
  }
9250
- AssetFileInputComponent.decorators = [
9439
+ TextFormInputComponent.id = 'text-form-input';
9440
+ TextFormInputComponent.decorators = [
9251
9441
  { type: Component, args: [{
9252
- selector: 'vdr-asset-file-input',
9253
- template: "<input type=\"file\" class=\"file-input\" #fileInput (change)=\"select($event)\" multiple [accept]=\"accept\" />\r\n<button class=\"btn btn-primary\" (click)=\"fileInput.click()\" [disabled]=\"uploading\">\r\n <ng-container *ngIf=\"uploading; else selectable\">\r\n <clr-spinner clrInline></clr-spinner>\r\n {{ 'asset.uploading' | translate }}\r\n </ng-container>\r\n <ng-template #selectable>\r\n <clr-icon shape=\"upload-cloud\"></clr-icon>\r\n {{ 'asset.upload-assets' | translate }}\r\n </ng-template>\r\n</button>\r\n<div\r\n class=\"drop-zone\"\r\n [ngStyle]=\"dropZoneStyle\"\r\n [class.visible]=\"dragging\"\r\n [class.dragging-over]=\"overDropZone\"\r\n (dragenter)=\"overDropZone = true\"\r\n (dragleave)=\"overDropZone = false\"\r\n (dragover)=\"onDragOver($event)\"\r\n (drop)=\"onDrop($event)\"\r\n #dropZone\r\n>\r\n <div class=\"drop-label\" (dragenter)=\"overDropZone = true\">\r\n <clr-icon shape=\"upload-cloud\" size=\"32\"></clr-icon>\r\n {{ 'catalog.drop-files-to-upload' | translate }}\r\n </div>\r\n</div>\r\n",
9442
+ selector: 'vdr-text-form-input',
9443
+ template: "<vdr-affixed-input\r\n [suffix]=\"suffix\"\r\n [prefix]=\"prefix\"\r\n>\r\n <input type=\"text\" [readonly]=\"readonly\" [formControl]=\"formControl\" />\r\n</vdr-affixed-input>\r\n",
9254
9444
  changeDetection: ChangeDetectionStrategy.OnPush,
9255
- styles: [".file-input{display:none}.drop-zone{position:fixed;background-color:var(--color-primary-500);border:3px dashed var(--color-component-border-300);opacity:0;visibility:hidden;z-index:1000;transition:opacity .2s,background-color .2s,visibility 0s .2s;display:flex;align-items:center;justify-content:center}.drop-zone.visible{opacity:.3;visibility:visible;transition:opacity .2s,background-color .2s,border .2s,visibility 0s}.drop-zone .drop-label{background-color:#fffc;border-radius:3px;padding:24px;font-size:32px;pointer-events:none;opacity:.5;transition:opacity .2s}.drop-zone.dragging-over{border-color:#fff;background-color:var(--color-primary-500);opacity:.7;transition:background-color .2s,border .2s}.drop-zone.dragging-over .drop-label{opacity:1}\n"]
9445
+ styles: ["input{width:100%}\n"]
9256
9446
  },] }
9257
- ];
9258
- AssetFileInputComponent.ctorParameters = () => [
9259
- { type: ServerConfigService }
9260
- ];
9261
- AssetFileInputComponent.propDecorators = {
9262
- dropZoneTarget: [{ type: Input }],
9263
- uploading: [{ type: Input }],
9264
- selectFiles: [{ type: Output }],
9265
- onDragEnter: [{ type: HostListener, args: ['document:dragenter',] }],
9266
- onDragLeave: [{ type: HostListener, args: ['document:dragleave', ['$event'],] }]
9267
- };
9447
+ ];
9268
9448
 
9269
- class AssetPreviewDialogComponent {
9270
- constructor(dataService) {
9271
- this.dataService = dataService;
9272
- }
9273
- ngOnInit() {
9274
- this.assetWithTags$ = of(this.asset).pipe(mergeMap(asset => {
9275
- if (this.hasTags(asset)) {
9276
- return of(asset);
9277
- }
9278
- else {
9279
- // tslint:disable-next-line:no-non-null-assertion
9280
- return this.dataService.product.getAsset(asset.id).mapSingle(data => data.asset);
9281
- }
9282
- }));
9283
- }
9284
- hasTags(asset) {
9285
- return asset.hasOwnProperty('tags');
9449
+ /**
9450
+ * @description
9451
+ * Uses textarea form input. This is the default input for `text` type fields.
9452
+ *
9453
+ * @docsCategory custom-input-components
9454
+ * @docsPage default-inputs
9455
+ */
9456
+ class TextareaFormInputComponent {
9457
+ get spellcheck() {
9458
+ return this.config.spellcheck === true;
9286
9459
  }
9287
9460
  }
9288
- AssetPreviewDialogComponent.decorators = [
9461
+ TextareaFormInputComponent.id = 'textarea-form-input';
9462
+ TextareaFormInputComponent.decorators = [
9289
9463
  { type: Component, args: [{
9290
- selector: 'vdr-asset-preview-dialog',
9291
- template: "<ng-template vdrDialogTitle>\r\n <div class=\"title-row\">\r\n {{ asset.name }}\r\n </div>\r\n</ng-template>\r\n\r\n<vdr-asset-preview\r\n *ngIf=\"assetWithTags$ | async as assetWithTags\"\r\n [asset]=\"assetWithTags\"\r\n (assetChange)=\"assetChanges = $event\"\r\n (editClick)=\"resolveWith()\"\r\n></vdr-asset-preview>\r\n",
9464
+ selector: 'vdr-textarea-form-input',
9465
+ template: "<textarea [spellcheck]=\"spellcheck\" autocomplete=\"off\" autocorrect=\"off\"\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n></textarea>\r\n",
9292
9466
  changeDetection: ChangeDetectionStrategy.OnPush,
9293
- styles: [":host{height:70vh}.update-button.hidden{visibility:hidden}\n"]
9467
+ styles: [":host textarea{resize:both;height:6rem;width:100%}\n"]
9294
9468
  },] }
9295
- ];
9296
- AssetPreviewDialogComponent.ctorParameters = () => [
9297
- { type: DataService }
9298
9469
  ];
9299
9470
 
9300
- class AssetGalleryComponent {
9301
- constructor(modalService) {
9302
- this.modalService = modalService;
9303
- /**
9304
- * If true, allows multiple assets to be selected by ctrl+clicking.
9305
- */
9306
- this.multiSelect = false;
9307
- this.canDelete = false;
9308
- this.selectionChange = new EventEmitter();
9309
- this.deleteAssets = new EventEmitter();
9310
- this.selection = [];
9311
- }
9312
- ngOnChanges() {
9313
- if (this.assets) {
9314
- for (const asset of this.selection) {
9315
- // Update and selected assets with any changes
9316
- const match = this.assets.find(a => a.id === asset.id);
9317
- if (match) {
9318
- Object.assign(asset, match);
9319
- }
9320
- }
9321
- }
9322
- }
9323
- toggleSelection(asset, event) {
9324
- const index = this.selection.findIndex(a => a.id === asset.id);
9325
- if (this.multiSelect && (event === null || event === void 0 ? void 0 : event.shiftKey) && 1 <= this.selection.length) {
9326
- const lastSelection = this.selection[this.selection.length - 1];
9327
- const lastSelectionIndex = this.assets.findIndex(a => a.id === lastSelection.id);
9328
- const currentIndex = this.assets.findIndex(a => a.id === asset.id);
9329
- const start = currentIndex < lastSelectionIndex ? currentIndex : lastSelectionIndex;
9330
- const end = currentIndex > lastSelectionIndex ? currentIndex + 1 : lastSelectionIndex;
9331
- this.selection.push(...this.assets.slice(start, end).filter(a => !this.selection.find(s => s.id === a.id)));
9332
- }
9333
- else if (index === -1) {
9334
- if (this.multiSelect && ((event === null || event === void 0 ? void 0 : event.ctrlKey) || (event === null || event === void 0 ? void 0 : event.shiftKey))) {
9335
- this.selection.push(asset);
9336
- }
9337
- else {
9338
- this.selection = [asset];
9339
- }
9340
- }
9341
- else {
9342
- if (this.multiSelect && (event === null || event === void 0 ? void 0 : event.ctrlKey)) {
9343
- this.selection.splice(index, 1);
9344
- }
9345
- else if (1 < this.selection.length) {
9346
- this.selection = [asset];
9347
- }
9348
- else {
9349
- this.selection.splice(index, 1);
9350
- }
9351
- }
9352
- // Make the selection mutable
9353
- this.selection = this.selection.map(x => (Object.assign({}, x)));
9354
- this.selectionChange.emit(this.selection);
9355
- }
9356
- selectMultiple(assets) {
9357
- this.selection = assets;
9358
- this.selectionChange.emit(this.selection);
9359
- }
9360
- isSelected(asset) {
9361
- return !!this.selection.find(a => a.id === asset.id);
9362
- }
9363
- lastSelected() {
9364
- return this.selection[this.selection.length - 1];
9365
- }
9366
- previewAsset(asset) {
9367
- this.modalService
9368
- .fromComponent(AssetPreviewDialogComponent, {
9369
- size: 'xl',
9370
- closable: true,
9371
- locals: { asset },
9372
- })
9373
- .subscribe();
9374
- }
9375
- entityInfoClick(event) {
9376
- event.preventDefault();
9377
- event.stopPropagation();
9378
- }
9379
- }
9380
- AssetGalleryComponent.decorators = [
9381
- { type: Component, args: [{
9382
- selector: 'vdr-asset-gallery',
9383
- template: "<div class=\"gallery\">\r\n <div\r\n class=\"card\"\r\n *ngFor=\"let asset of assets\"\r\n (click)=\"toggleSelection(asset, $event)\"\r\n [class.selected]=\"isSelected(asset)\"\r\n >\r\n <div class=\"card-img\">\r\n <div class=\"selected-checkbox\"><clr-icon shape=\"check-circle\" size=\"32\"></clr-icon></div>\r\n <img [src]=\"asset | assetPreview: 'thumb'\" />\r\n </div>\r\n <div class=\"detail\">\r\n <vdr-entity-info\r\n [entity]=\"asset\"\r\n [small]=\"true\"\r\n (click)=\"entityInfoClick($event)\"\r\n ></vdr-entity-info>\r\n <span [title]=\"asset.name\">{{ asset.name }}</span>\r\n </div>\r\n </div>\r\n</div>\r\n<div class=\"info-bar\">\r\n <div class=\"card\">\r\n <div class=\"card-img\">\r\n <div class=\"placeholder\" *ngIf=\"selection.length === 0\">\r\n <clr-icon shape=\"image\" size=\"128\"></clr-icon>\r\n <div>{{ 'catalog.no-selection' | translate }}</div>\r\n </div>\r\n <img\r\n class=\"preview\"\r\n *ngIf=\"selection.length >= 1\"\r\n [src]=\"lastSelected().preview + '?preset=medium'\"\r\n />\r\n </div>\r\n <div class=\"card-block details\" *ngIf=\"selection.length >= 1\">\r\n <div class=\"name\">{{ lastSelected().name }}</div>\r\n <div>{{ 'asset.original-asset-size' | translate }}: {{ lastSelected().fileSize | filesize }}</div>\r\n\r\n <ng-container *ngIf=\"selection.length === 1\">\r\n <vdr-chip *ngFor=\"let tag of lastSelected().tags\" [colorFrom]=\"tag.value\"\r\n ><clr-icon shape=\"tag\" class=\"mr2\"></clr-icon> {{ tag.value }}</vdr-chip\r\n >\r\n <div>\r\n <button (click)=\"previewAsset(lastSelected())\" class=\"btn btn-link\">\r\n <clr-icon shape=\"eye\"></clr-icon> {{ 'asset.preview' | translate }}\r\n </button>\r\n </div>\r\n <div>\r\n <vdr-asset-preview-links class=\"\" [asset]=\"lastSelected()\"></vdr-asset-preview-links>\r\n </div>\r\n <div>\r\n <a [routerLink]=\"['./', lastSelected().id]\" class=\"btn btn-link\">\r\n <clr-icon shape=\"pencil\"></clr-icon> {{ 'common.edit' | translate }}\r\n </a>\r\n </div>\r\n </ng-container>\r\n <div *ngIf=\"canDelete\">\r\n <button (click)=\"deleteAssets.emit(selection)\" class=\"btn btn-link\">\r\n <clr-icon shape=\"trash\" class=\"is-danger\"></clr-icon> {{ 'common.delete' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card stack\" [class.visible]=\"selection.length > 1\"></div>\r\n <div class=\"selection-count\" [class.visible]=\"selection.length > 1\">\r\n {{ 'asset.assets-selected-count' | translate: { count: selection.length } }}\r\n <ul>\r\n <li *ngFor=\"let asset of selection\">{{ asset.name }}</li>\r\n </ul>\r\n </div>\r\n</div>\r\n",
9384
- changeDetection: ChangeDetectionStrategy.OnPush,
9385
- styles: [":host{display:flex;overflow:hidden}.gallery{flex:1;display:grid;grid-template-columns:repeat(auto-fill,150px);grid-template-rows:repeat(auto-fill,180px);grid-gap:10px 20px;overflow-y:auto;padding-left:12px;padding-top:12px;padding-bottom:12px}.gallery .card:hover{box-shadow:0 .125rem 0 0 var(--color-primary-500);border:1px solid var(--color-primary-500)}.card{margin-top:0;position:relative}.selected-checkbox{opacity:0;position:absolute;color:var(--color-success-500);background-color:#fff;border-radius:50%;top:-12px;left:-12px;box-shadow:0 5px 5px -4px #000000bf;transition:opacity .1s}.card.selected{box-shadow:0 .125rem 0 0 var(--color-primary-500);border:1px solid var(--color-primary-500)}.card.selected .selected-checkbox{opacity:1}.detail{font-size:12px;margin:3px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.detail vdr-entity-info{height:16px}.info-bar{width:25%;padding:0 6px;overflow-y:auto}.info-bar .card{z-index:1}.info-bar .stack{z-index:0;opacity:0;transform:perspective(500px) translateZ(0) translateY(-16px);height:16px;transition:transform .3s,opacity 0s .3s;background-color:#fff}.info-bar .stack.visible{opacity:1;transform:perspective(500px) translateZ(-44px) translateY(0);background-color:var(--color-component-bg-100);transition:transform .3s,color .3s}.info-bar .selection-count{opacity:0;position:relative;text-align:center;visibility:hidden;transition:opacity .3s,visibility 0s .3s}.info-bar .selection-count.visible{opacity:1;visibility:visible;transition:opacity .3s,visibility 0s}.info-bar .selection-count ul{text-align:left;list-style-type:none;margin-left:12px}.info-bar .selection-count ul li{font-size:12px}.info-bar .placeholder{text-align:center;color:var(--color-grey-300)}.info-bar .preview img{max-width:100%}.info-bar .details{font-size:12px;word-break:break-all}.info-bar .name{line-height:14px;font-weight:bold}\n"]
9386
- },] }
9387
- ];
9388
- AssetGalleryComponent.ctorParameters = () => [
9389
- { type: ModalService }
9471
+ const defaultFormInputs = [
9472
+ BooleanFormInputComponent,
9473
+ CurrencyFormInputComponent,
9474
+ DateFormInputComponent,
9475
+ FacetValueFormInputComponent,
9476
+ NumberFormInputComponent,
9477
+ SelectFormInputComponent,
9478
+ TextFormInputComponent,
9479
+ ProductSelectorFormInputComponent,
9480
+ CustomerGroupFormInputComponent,
9481
+ PasswordFormInputComponent,
9482
+ RelationFormInputComponent,
9483
+ TextareaFormInputComponent,
9484
+ RichTextFormInputComponent,
9485
+ JsonEditorFormInputComponent,
9486
+ ProductMultiSelectorFormInputComponent,
9487
+ CombinationModeFormInputComponent,
9390
9488
  ];
9391
- AssetGalleryComponent.propDecorators = {
9392
- assets: [{ type: Input }],
9393
- multiSelect: [{ type: Input }],
9394
- canDelete: [{ type: Input }],
9395
- selectionChange: [{ type: Output }],
9396
- deleteAssets: [{ type: Output }]
9397
- };
9398
-
9399
9489
  /**
9400
9490
  * @description
9401
- * A dialog which allows the creation and selection of assets.
9491
+ * Registers a custom FormInputComponent which can be used to control the argument inputs
9492
+ * of a {@link ConfigurableOperationDef} (e.g. CollectionFilter, ShippingMethod etc) or for
9493
+ * a custom field.
9402
9494
  *
9403
9495
  * @example
9404
9496
  * ```TypeScript
9405
- * selectAssets() {
9406
- * this.modalService
9407
- * .fromComponent(AssetPickerDialogComponent, {
9408
- * size: 'xl',
9409
- * })
9410
- * .subscribe(result => {
9411
- * if (result && result.length) {
9412
- * // ...
9413
- * }
9414
- * });
9497
+ * \@NgModule({
9498
+ * imports: [SharedModule],
9499
+ * declarations: [MyCustomFieldControl],
9500
+ * providers: [
9501
+ * registerFormInputComponent('my-custom-input', MyCustomFieldControl),
9502
+ * ],
9503
+ * })
9504
+ * export class MyUiExtensionModule {}
9505
+ * ```
9506
+ *
9507
+ * This input component can then be used in a custom field:
9508
+ *
9509
+ * @example
9510
+ * ```TypeScript
9511
+ * const config = {
9512
+ * // ...
9513
+ * customFields: {
9514
+ * ProductVariant: [
9515
+ * {
9516
+ * name: 'rrp',
9517
+ * type: 'int',
9518
+ * ui: { component: 'my-custom-input' },
9519
+ * },
9520
+ * ]
9521
+ * }
9415
9522
  * }
9416
9523
  * ```
9417
9524
  *
9418
- * @docsCategory components
9525
+ * or with an argument of a {@link ConfigurableOperationDef}:
9526
+ *
9527
+ * @example
9528
+ * ```TypeScript
9529
+ * args: {
9530
+ * rrp: { type: 'int', ui: { component: 'my-custom-input' } },
9531
+ * }
9532
+ * ```
9533
+ *
9534
+ * @docsCategory custom-input-components
9419
9535
  */
9420
- class AssetPickerDialogComponent {
9421
- constructor(dataService, notificationService) {
9536
+ function registerFormInputComponent(id, component) {
9537
+ return {
9538
+ provide: APP_INITIALIZER,
9539
+ multi: true,
9540
+ useFactory: (registry) => () => {
9541
+ registry.registerInputComponent(id, component);
9542
+ },
9543
+ deps: [ComponentRegistryService],
9544
+ };
9545
+ }
9546
+ /**
9547
+ * @description
9548
+ * **Deprecated** use `registerFormInputComponent()` in combination with the customField `ui` config instead.
9549
+ *
9550
+ * Registers a custom component to act as the form input control for the given custom field.
9551
+ * This should be used in the NgModule `providers` array of your ui extension module.
9552
+ *
9553
+ * @example
9554
+ * ```TypeScript
9555
+ * \@NgModule({
9556
+ * imports: [SharedModule],
9557
+ * declarations: [MyCustomFieldControl],
9558
+ * providers: [
9559
+ * registerCustomFieldComponent('Product', 'someCustomField', MyCustomFieldControl),
9560
+ * ],
9561
+ * })
9562
+ * export class MyUiExtensionModule {}
9563
+ * ```
9564
+ *
9565
+ * @deprecated use `registerFormInputComponent()` in combination with the customField `ui` config instead.
9566
+ *
9567
+ * @docsCategory custom-input-components
9568
+ */
9569
+ function registerCustomFieldComponent(entity, fieldName, component) {
9570
+ return {
9571
+ provide: APP_INITIALIZER,
9572
+ multi: true,
9573
+ useFactory: (customFieldComponentService) => () => {
9574
+ customFieldComponentService.registerCustomFieldComponent(entity, fieldName, component);
9575
+ },
9576
+ deps: [CustomFieldComponentService],
9577
+ };
9578
+ }
9579
+ /**
9580
+ * Registers the default form input components.
9581
+ */
9582
+ function registerDefaultFormInputs() {
9583
+ return defaultFormInputs.map(cmp => registerFormInputComponent(cmp.id, cmp));
9584
+ }
9585
+
9586
+ class ActionBarItemsComponent {
9587
+ constructor(navBuilderService, route, dataService, notificationService) {
9588
+ this.navBuilderService = navBuilderService;
9589
+ this.route = route;
9422
9590
  this.dataService = dataService;
9423
9591
  this.notificationService = notificationService;
9424
- this.paginationConfig = {
9425
- currentPage: 1,
9426
- itemsPerPage: 25,
9427
- totalItems: 1,
9428
- };
9429
- this.multiSelect = true;
9430
- this.initialTags = [];
9431
- this.selection = [];
9432
- this.searchTerm$ = new BehaviorSubject(undefined);
9433
- this.filterByTags$ = new BehaviorSubject(undefined);
9434
- this.uploading = false;
9435
- this.destroy$ = new Subject();
9592
+ this.locationId$ = new BehaviorSubject('');
9436
9593
  }
9437
9594
  ngOnInit() {
9438
- this.listQuery = this.dataService.product.getAssetList(this.paginationConfig.itemsPerPage, 0);
9439
- this.allTags$ = this.dataService.product.getTagList().mapSingle(data => data.tags.items);
9440
- this.assets$ = this.listQuery.stream$.pipe(tap(result => (this.paginationConfig.totalItems = result.assets.totalItems)), map(result => result.assets.items));
9441
- this.searchTerm$.pipe(debounceTime(250), takeUntil(this.destroy$)).subscribe(() => {
9442
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9443
- });
9444
- this.filterByTags$.pipe(debounceTime(100), takeUntil(this.destroy$)).subscribe(() => {
9445
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9446
- });
9595
+ this.items$ = combineLatest(this.navBuilderService.actionBarConfig$, this.locationId$).pipe(map(([items, locationId]) => items.filter(config => config.locationId === locationId)));
9447
9596
  }
9448
- ngAfterViewInit() {
9449
- if (0 < this.initialTags.length) {
9450
- this.allTags$
9451
- .pipe(take(1), map(allTags => allTags.filter(tag => this.initialTags.includes(tag.value))), tap(tags => this.filterByTags$.next(tags)), delay(1))
9452
- .subscribe(tags => this.assetSearchInputComponent.setTags(tags));
9597
+ ngOnChanges(changes) {
9598
+ if ('locationId' in changes) {
9599
+ this.locationId$.next(changes['locationId'].currentValue);
9453
9600
  }
9454
9601
  }
9455
- ngOnDestroy() {
9456
- this.destroy$.next();
9457
- this.destroy$.complete();
9458
- }
9459
- pageChange(page) {
9460
- this.paginationConfig.currentPage = page;
9461
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9462
- }
9463
- itemsPerPageChange(itemsPerPage) {
9464
- this.paginationConfig.itemsPerPage = itemsPerPage;
9465
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9466
- }
9467
- cancel() {
9468
- this.resolveWith();
9602
+ handleClick(event, item) {
9603
+ if (typeof item.onClick === 'function') {
9604
+ item.onClick(event, {
9605
+ route: this.route,
9606
+ dataService: this.dataService,
9607
+ notificationService: this.notificationService,
9608
+ });
9609
+ }
9469
9610
  }
9470
- select() {
9471
- this.resolveWith(this.selection);
9611
+ getRouterLink(item) {
9612
+ return this.navBuilderService.getRouterLink(item, this.route);
9472
9613
  }
9473
- createAssets(files) {
9474
- if (files.length) {
9475
- this.uploading = true;
9476
- this.dataService.product
9477
- .createAssets(files)
9478
- .pipe(finalize(() => (this.uploading = false)))
9479
- .subscribe(res => {
9480
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9481
- this.notificationService.success(marker('asset.notify-create-assets-success'), {
9482
- count: files.length,
9483
- });
9484
- const assets = res.createAssets.filter(a => a.__typename === 'Asset');
9485
- this.assetGalleryComponent.selectMultiple(assets);
9486
- });
9614
+ getButtonStyles(item) {
9615
+ const styles = ['btn'];
9616
+ if (item.buttonStyle && item.buttonStyle === 'link') {
9617
+ styles.push('btn-link');
9618
+ return styles;
9487
9619
  }
9620
+ styles.push(this.getButtonColorClass(item));
9621
+ return styles;
9488
9622
  }
9489
- fetchPage(currentPage, itemsPerPage) {
9490
- var _a;
9491
- const take = +itemsPerPage;
9492
- const skip = (currentPage - 1) * +itemsPerPage;
9493
- const searchTerm = this.searchTerm$.value;
9494
- const tags = (_a = this.filterByTags$.value) === null || _a === void 0 ? void 0 : _a.map(t => t.value);
9495
- this.listQuery.ref.refetch({
9496
- options: {
9497
- skip,
9498
- take,
9499
- filter: {
9500
- name: {
9501
- contains: searchTerm,
9502
- },
9503
- },
9504
- sort: {
9505
- createdAt: SortOrder.DESC,
9506
- },
9507
- tags,
9508
- tagsOperator: LogicalOperator.AND,
9509
- },
9510
- });
9623
+ getButtonColorClass(item) {
9624
+ switch (item.buttonColor) {
9625
+ case undefined:
9626
+ case 'primary':
9627
+ return item.buttonStyle === 'outline' ? 'btn-outline' : 'btn-primary';
9628
+ case 'success':
9629
+ return item.buttonStyle === 'outline' ? 'btn-success-outline' : 'btn-success';
9630
+ case 'warning':
9631
+ return item.buttonStyle === 'outline' ? 'btn-warning-outline' : 'btn-warning';
9632
+ default:
9633
+ assertNever(item.buttonColor);
9634
+ return '';
9635
+ }
9511
9636
  }
9512
9637
  }
9513
- AssetPickerDialogComponent.decorators = [
9638
+ ActionBarItemsComponent.decorators = [
9514
9639
  { type: Component, args: [{
9515
- selector: 'vdr-asset-picker-dialog',
9516
- template: "<ng-template vdrDialogTitle>\r\n <div class=\"title-row\">\r\n <span>{{ 'asset.select-assets' | translate }}</span>\r\n <div class=\"flex-spacer\"></div>\r\n <vdr-asset-file-input\r\n class=\"ml3\"\r\n (selectFiles)=\"createAssets($event)\"\r\n [uploading]=\"uploading\"\r\n dropZoneTarget=\".modal-content\"\r\n ></vdr-asset-file-input>\r\n </div>\r\n</ng-template>\r\n<vdr-asset-search-input\r\n class=\"mb2\"\r\n [tags]=\"allTags$ | async\"\r\n (searchTermChange)=\"searchTerm$.next($event)\"\r\n (tagsChange)=\"filterByTags$.next($event)\"\r\n #assetSearchInputComponent\r\n></vdr-asset-search-input>\r\n<vdr-asset-gallery\r\n [assets]=\"(assets$ | async)! | paginate: paginationConfig\"\r\n [multiSelect]=\"multiSelect\"\r\n (selectionChange)=\"selection = $event\"\r\n #assetGalleryComponent\r\n></vdr-asset-gallery>\r\n\r\n<div class=\"paging-controls\">\r\n <vdr-items-per-page-controls\r\n [itemsPerPage]=\"paginationConfig.itemsPerPage\"\r\n (itemsPerPageChange)=\"itemsPerPageChange($event)\"\r\n ></vdr-items-per-page-controls>\r\n\r\n <vdr-pagination-controls\r\n [currentPage]=\"paginationConfig.currentPage\"\r\n [itemsPerPage]=\"paginationConfig.itemsPerPage\"\r\n [totalItems]=\"paginationConfig.totalItems\"\r\n (pageChange)=\"pageChange($event)\"\r\n ></vdr-pagination-controls>\r\n</div>\r\n\r\n<ng-template vdrDialogButtons>\r\n <button type=\"button\" class=\"btn\" (click)=\"cancel()\">{{ 'common.cancel' | translate }}</button>\r\n <button type=\"submit\" (click)=\"select()\" class=\"btn btn-primary\" [disabled]=\"selection.length === 0\">\r\n {{ 'asset.add-asset-with-count' | translate: { count: selection.length } }}\r\n </button>\r\n</ng-template>\r\n",
9517
- changeDetection: ChangeDetectionStrategy.OnPush,
9518
- styles: [":host{display:flex;flex-direction:column;height:70vh}.title-row{display:flex;align-items:center;justify-content:space-between}vdr-asset-gallery{flex:1}.paging-controls{padding-top:6px;border-top:1px solid var(--color-component-border-100);display:flex;justify-content:space-between;flex-shrink:0}\n"]
9640
+ selector: 'vdr-action-bar-items',
9641
+ template: "<vdr-ui-extension-point [locationId]=\"locationId\" api=\"actionBar\" [leftPx]=\"-24\" [topPx]=\"-6\">\r\n <ng-container *ngFor=\"let item of items$ | async\">\r\n <button\r\n *vdrIfPermissions=\"item.requiresPermission\"\r\n [routerLink]=\"getRouterLink(item)\"\r\n [disabled]=\"item.disabled ? (item.disabled | async) : false\"\r\n (click)=\"handleClick($event, item)\"\r\n [ngClass]=\"getButtonStyles(item)\"\r\n >\r\n <clr-icon *ngIf=\"item.icon\" [attr.shape]=\"item.icon\"></clr-icon>\r\n {{ item.label | translate }}\r\n </button>\r\n </ng-container>\r\n</vdr-ui-extension-point>\r\n",
9642
+ changeDetection: ChangeDetectionStrategy.OnPush,
9643
+ styles: [":host{display:inline-block;min-height:36px}\n"]
9519
9644
  },] }
9520
9645
  ];
9521
- AssetPickerDialogComponent.ctorParameters = () => [
9646
+ ActionBarItemsComponent.ctorParameters = () => [
9647
+ { type: NavBuilderService },
9648
+ { type: ActivatedRoute },
9522
9649
  { type: DataService },
9523
9650
  { type: NotificationService }
9524
9651
  ];
9525
- AssetPickerDialogComponent.propDecorators = {
9526
- assetSearchInputComponent: [{ type: ViewChild, args: ['assetSearchInputComponent',] }],
9527
- assetGalleryComponent: [{ type: ViewChild, args: ['assetGalleryComponent',] }]
9652
+ ActionBarItemsComponent.propDecorators = {
9653
+ locationId: [{ type: HostBinding, args: ['attr.data-location-id',] }, { type: Input }]
9528
9654
  };
9529
9655
 
9530
- class AssetPreviewLinksComponent {
9656
+ class ActionBarLeftComponent {
9531
9657
  constructor() {
9532
- this.sizes = ['tiny', 'thumb', 'small', 'medium', 'large', 'full'];
9658
+ this.grow = false;
9533
9659
  }
9534
9660
  }
9535
- AssetPreviewLinksComponent.decorators = [
9661
+ ActionBarLeftComponent.decorators = [
9536
9662
  { type: Component, args: [{
9537
- selector: 'vdr-asset-preview-links',
9538
- template: "<vdr-dropdown>\r\n <button class=\"btn btn-link\" vdrDropdownTrigger>\r\n <clr-icon shape=\"link\"></clr-icon> {{ 'catalog.asset-preview-links' | translate }}<clr-icon shape=\"caret\" dir=\"down\"></clr-icon>\r\n </button>\r\n <vdr-dropdown-menu vdrPosition=\"bottom-left\">\r\n <a\r\n *ngFor=\"let size of sizes\"\r\n [href]=\"asset | assetPreview: size\"\r\n [title]=\"asset | assetPreview: size\"\r\n target=\"_blank\"\r\n class=\"asset-preview-link\"\r\n vdrDropdownItem\r\n >\r\n <vdr-chip><clr-icon shape=\"link\"></clr-icon> {{ 'asset.preview' | translate }}: {{ size }}</vdr-chip>\r\n </a>\r\n </vdr-dropdown-menu></vdr-dropdown\r\n>\r\n",
9663
+ selector: 'vdr-ab-left',
9664
+ template: `
9665
+ <ng-content></ng-content>
9666
+ `
9667
+ },] }
9668
+ ];
9669
+ ActionBarLeftComponent.propDecorators = {
9670
+ grow: [{ type: Input }]
9671
+ };
9672
+ class ActionBarRightComponent {
9673
+ constructor() {
9674
+ this.grow = false;
9675
+ }
9676
+ }
9677
+ ActionBarRightComponent.decorators = [
9678
+ { type: Component, args: [{
9679
+ selector: 'vdr-ab-right',
9680
+ template: `
9681
+ <ng-content></ng-content>
9682
+ `
9683
+ },] }
9684
+ ];
9685
+ ActionBarRightComponent.propDecorators = {
9686
+ grow: [{ type: Input }]
9687
+ };
9688
+ class ActionBarComponent {
9689
+ }
9690
+ ActionBarComponent.decorators = [
9691
+ { type: Component, args: [{
9692
+ selector: 'vdr-action-bar',
9693
+ template: "<div class=\"left-content\" [class.grow]=\"left?.grow\"><ng-content select=\"vdr-ab-left\"></ng-content></div>\r\n<div class=\"right-content\" [class.grow]=\"right?.grow\"><ng-content select=\"vdr-ab-right\"></ng-content></div>\r\n",
9694
+ styles: [":host{display:flex;justify-content:space-between;align-items:baseline;background-color:var(--color-component-bg-100);position:sticky;top:-24px;z-index:25;border-bottom:1px solid var(--color-component-border-200)}:host>.grow{flex:1}\n"]
9695
+ },] }
9696
+ ];
9697
+ ActionBarComponent.propDecorators = {
9698
+ left: [{ type: ContentChild, args: [ActionBarLeftComponent,] }],
9699
+ right: [{ type: ContentChild, args: [ActionBarRightComponent,] }]
9700
+ };
9701
+
9702
+ class AddressFormComponent {
9703
+ }
9704
+ AddressFormComponent.decorators = [
9705
+ { type: Component, args: [{
9706
+ selector: 'vdr-address-form',
9707
+ template: "<form [formGroup]=\"formGroup\">\r\n <clr-input-container>\r\n <label>{{ 'customer.full-name' | translate }}</label>\r\n <input formControlName=\"fullName\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n\r\n <div class=\"clr-row\">\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.street-line-1' | translate }}</label>\r\n <input formControlName=\"streetLine1\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.street-line-2' | translate }}</label>\r\n <input formControlName=\"streetLine2\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n </div>\r\n <div class=\"clr-row\">\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.city' | translate }}</label>\r\n <input formControlName=\"city\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.province' | translate }}</label>\r\n <input formControlName=\"province\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n </div>\r\n <div class=\"clr-row\">\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.postal-code' | translate }}</label>\r\n <input formControlName=\"postalCode\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n </div>\r\n <div class=\"clr-col-md-4\">\r\n <clr-input-container>\r\n <label>{{ 'customer.country' | translate }}</label>\r\n <select name=\"countryCode\" formControlName=\"countryCode\" clrInput clrSelect>\r\n <option *ngFor=\"let country of availableCountries\" [value]=\"country.code\">\r\n {{ country.name }}\r\n </option>\r\n </select>\r\n </clr-input-container>\r\n </div>\r\n </div>\r\n <clr-input-container>\r\n <label>{{ 'customer.phone-number' | translate }}</label>\r\n <input formControlName=\"phoneNumber\" type=\"text\" clrInput/>\r\n </clr-input-container>\r\n <section formGroupName=\"customFields\" *ngIf=\"formGroup.get('customFields') as customFieldsGroup\">\r\n <label>{{ 'common.custom-fields' | translate }}</label>\r\n <vdr-tabbed-custom-fields\r\n entityName=\"Address\"\r\n [customFields]=\"customFields\"\r\n [customFieldsFormGroup]=\"customFieldsGroup\"\r\n ></vdr-tabbed-custom-fields>\r\n </section>\r\n</form>\r\n",
9539
9708
  changeDetection: ChangeDetectionStrategy.OnPush,
9540
- styles: [".asset-preview-link{font-size:12px}\n"]
9709
+ styles: [""]
9541
9710
  },] }
9542
9711
  ];
9543
- AssetPreviewLinksComponent.propDecorators = {
9544
- asset: [{ type: Input }]
9712
+ AddressFormComponent.propDecorators = {
9713
+ customFields: [{ type: Input }],
9714
+ formGroup: [{ type: Input }],
9715
+ availableCountries: [{ type: Input }]
9545
9716
  };
9546
9717
 
9547
- class ManageTagsDialogComponent {
9548
- constructor(dataService) {
9549
- this.dataService = dataService;
9550
- this.toDelete = [];
9551
- this.toUpdate = [];
9552
- }
9553
- ngOnInit() {
9554
- this.allTags$ = this.dataService.product.getTagList().mapStream(data => data.tags.items);
9718
+ /**
9719
+ * A wrapper around an <input> element which adds a prefix and/or a suffix element.
9720
+ */
9721
+ class AffixedInputComponent {
9722
+ }
9723
+ AffixedInputComponent.decorators = [
9724
+ { type: Component, args: [{
9725
+ selector: 'vdr-affixed-input',
9726
+ template: "<div [class.has-prefix]=\"!!prefix\" [class.has-suffix]=\"!!suffix\">\r\n <ng-content></ng-content>\r\n</div>\r\n<div class=\"affix prefix\" *ngIf=\"prefix\">{{ prefix }}</div>\r\n<div class=\"affix suffix\" *ngIf=\"suffix\">{{ suffix }}</div>\r\n",
9727
+ changeDetection: ChangeDetectionStrategy.OnPush,
9728
+ styles: [":host{display:inline-flex}.affix{color:var(--color-grey-800);display:flex;align-items:center;background-color:var(--color-grey-200);border:1px solid var(--color-component-border-300);top:1px;padding:3px;line-height:.58333rem;transition:border .2s}::ng-deep .has-prefix input{border-top-left-radius:0!important;border-bottom-left-radius:0!important}.prefix{order:-1;border-radius:3px 0 0 3px;border-right:none}::ng-deep .has-suffix input{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.suffix{border-radius:0 3px 3px 0;border-left:none}\n"]
9729
+ },] }
9730
+ ];
9731
+ AffixedInputComponent.propDecorators = {
9732
+ prefix: [{ type: Input }],
9733
+ suffix: [{ type: Input }]
9734
+ };
9735
+
9736
+ /**
9737
+ * A form input control which displays a number input with a percentage sign suffix.
9738
+ */
9739
+ class PercentageSuffixInputComponent {
9740
+ constructor() {
9741
+ this.disabled = false;
9742
+ this.readonly = false;
9555
9743
  }
9556
- toggleDelete(id) {
9557
- const marked = this.markedAsDeleted(id);
9558
- if (marked) {
9559
- this.toDelete = this.toDelete.filter(_id => _id !== id);
9560
- }
9561
- else {
9562
- this.toDelete.push(id);
9744
+ ngOnChanges(changes) {
9745
+ if ('value' in changes) {
9746
+ this.writeValue(changes['value'].currentValue);
9563
9747
  }
9564
9748
  }
9565
- markedAsDeleted(id) {
9566
- return this.toDelete.includes(id);
9749
+ registerOnChange(fn) {
9750
+ this.onChange = fn;
9567
9751
  }
9568
- updateTagValue(id, value) {
9569
- const exists = this.toUpdate.find(i => i.id === id);
9570
- if (exists) {
9571
- exists.value = value;
9572
- }
9573
- else {
9574
- this.toUpdate.push({ id, value });
9575
- }
9752
+ registerOnTouched(fn) {
9753
+ this.onTouch = fn;
9576
9754
  }
9577
- saveChanges() {
9578
- const operations = [];
9579
- for (const id of this.toDelete) {
9580
- operations.push(this.dataService.product.deleteTag(id));
9581
- }
9582
- for (const item of this.toUpdate) {
9583
- if (!this.toDelete.includes(item.id)) {
9584
- operations.push(this.dataService.product.updateTag(item));
9585
- }
9755
+ setDisabledState(isDisabled) {
9756
+ this.disabled = isDisabled;
9757
+ }
9758
+ onInput(value) {
9759
+ this.onChange(value);
9760
+ }
9761
+ writeValue(value) {
9762
+ const numericValue = +value;
9763
+ if (!Number.isNaN(numericValue)) {
9764
+ this._value = numericValue;
9586
9765
  }
9587
- return forkJoin(operations).subscribe(() => this.resolveWith(true));
9588
9766
  }
9589
9767
  }
9590
- ManageTagsDialogComponent.decorators = [
9768
+ PercentageSuffixInputComponent.decorators = [
9591
9769
  { type: Component, args: [{
9592
- selector: 'vdr-manage-tags-dialog',
9593
- template: "<ng-template vdrDialogTitle>\r\n <span>{{ 'common.manage-tags' | translate }}</span>\r\n</ng-template>\r\n<p class=\"mt0 mb4\">{{ 'common.manage-tags-description' | translate }}</p>\r\n<ul class=\"tag-list\" *ngFor=\"let tag of allTags$ | async\">\r\n <li class=\"mb2 p1\" [class.to-delete]=\"markedAsDeleted(tag.id)\">\r\n <clr-icon shape=\"tag\" class=\"is-solid mr2\" [style.color]=\"tag.value | stringToColor\"></clr-icon>\r\n <input type=\"text\" (input)=\"updateTagValue(tag.id, $event.target.value)\" [value]=\"tag.value\" />\r\n <button class=\"icon-button\" (click)=\"toggleDelete(tag.id)\">\r\n <clr-icon shape=\"trash\" class=\"is-danger\" [class.is-solid]=\"markedAsDeleted(tag.id)\"></clr-icon>\r\n </button>\r\n </li>\r\n</ul>\r\n<ng-template vdrDialogButtons>\r\n <button type=\"submit\" (click)=\"resolveWith(false)\" class=\"btn btn-secondary\">\r\n {{ 'common.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"submit\"\r\n (click)=\"saveChanges()\"\r\n class=\"btn btn-primary\"\r\n [disabled]=\"!toUpdate.length && !toDelete.length\"\r\n >\r\n {{ 'common.update' | translate }}\r\n </button>\r\n</ng-template>\r\n",
9594
- changeDetection: ChangeDetectionStrategy.OnPush,
9595
- styles: [".tag-list{list-style-type:none}.tag-list li{display:flex;align-items:center}.tag-list li input{max-width:170px}.tag-list li.to-delete{opacity:.7;background-color:var(--color-component-bg-300)}.tag-list li.to-delete input{background-color:transparent!important}\n"]
9770
+ selector: 'vdr-percentage-suffix-input',
9771
+ template: `
9772
+ <vdr-affixed-input suffix="%">
9773
+ <input
9774
+ type="number"
9775
+ step="1"
9776
+ [value]="_value"
9777
+ [disabled]="disabled"
9778
+ [readonly]="readonly"
9779
+ (input)="onInput($event.target.value)"
9780
+ (focus)="onTouch()"
9781
+ />
9782
+ </vdr-affixed-input>
9783
+ `,
9784
+ providers: [
9785
+ {
9786
+ provide: NG_VALUE_ACCESSOR,
9787
+ useExisting: PercentageSuffixInputComponent,
9788
+ multi: true,
9789
+ },
9790
+ ],
9791
+ styles: [`
9792
+ :host {
9793
+ padding: 0;
9794
+ }
9795
+ `]
9596
9796
  },] }
9597
9797
  ];
9598
- ManageTagsDialogComponent.ctorParameters = () => [
9599
- { type: DataService }
9600
- ];
9798
+ PercentageSuffixInputComponent.propDecorators = {
9799
+ disabled: [{ type: Input }],
9800
+ readonly: [{ type: Input }],
9801
+ value: [{ type: Input }]
9802
+ };
9601
9803
 
9602
- class AssetPreviewComponent {
9603
- constructor(formBuilder, dataService, notificationService, changeDetector, modalService) {
9604
- this.formBuilder = formBuilder;
9605
- this.dataService = dataService;
9606
- this.notificationService = notificationService;
9607
- this.changeDetector = changeDetector;
9608
- this.modalService = modalService;
9609
- this.editable = false;
9610
- this.customFields = [];
9611
- this.assetChange = new EventEmitter();
9612
- this.editClick = new EventEmitter();
9613
- this.size = 'medium';
9614
- this.width = 0;
9615
- this.height = 0;
9616
- this.centered = true;
9617
- this.settingFocalPoint = false;
9618
- }
9619
- get fpx() {
9620
- return this.asset.focalPoint ? this.asset.focalPoint.x : null;
9621
- }
9622
- get fpy() {
9623
- return this.asset.focalPoint ? this.asset.focalPoint.y : null;
9804
+ /**
9805
+ * A component for selecting files to upload as new Assets.
9806
+ */
9807
+ class AssetFileInputComponent {
9808
+ constructor(serverConfig) {
9809
+ this.serverConfig = serverConfig;
9810
+ /**
9811
+ * CSS selector of the DOM element which will be masked by the file
9812
+ * drop zone. Defaults to `body`.
9813
+ */
9814
+ this.dropZoneTarget = 'body';
9815
+ this.uploading = false;
9816
+ this.selectFiles = new EventEmitter();
9817
+ this.dragging = false;
9818
+ this.overDropZone = false;
9819
+ this.dropZoneStyle = {
9820
+ 'width.px': 0,
9821
+ 'height.px': 0,
9822
+ 'top.px': 0,
9823
+ 'left.px': 0,
9824
+ };
9624
9825
  }
9625
9826
  ngOnInit() {
9626
- var _a;
9627
- const { focalPoint } = this.asset;
9628
- this.form = this.formBuilder.group({
9629
- name: [this.asset.name],
9630
- tags: [(_a = this.asset.tags) === null || _a === void 0 ? void 0 : _a.map(t => t.value)],
9631
- });
9632
- this.subscription = this.form.valueChanges.subscribe(value => {
9633
- this.assetChange.emit({
9634
- id: this.asset.id,
9635
- name: value.name,
9636
- tags: value.tags,
9637
- });
9638
- });
9639
- this.subscription.add(fromEvent(window, 'resize')
9640
- .pipe(debounceTime(50))
9641
- .subscribe(() => {
9642
- this.updateDimensions();
9643
- this.changeDetector.markForCheck();
9644
- }));
9827
+ this.accept = this.serverConfig.serverConfig.permittedAssetTypes.join(',');
9828
+ this.fitDropZoneToTarget();
9645
9829
  }
9646
- ngOnDestroy() {
9647
- if (this.subscription) {
9648
- this.subscription.unsubscribe();
9649
- }
9650
- }
9651
- getSourceFileName() {
9652
- const parts = this.asset.source.split(/[\\\/]/g);
9653
- return parts[parts.length - 1];
9654
- }
9655
- onImageLoad() {
9656
- this.updateDimensions();
9657
- this.changeDetector.markForCheck();
9830
+ onDragEnter() {
9831
+ this.dragging = true;
9832
+ this.fitDropZoneToTarget();
9658
9833
  }
9659
- updateDimensions() {
9660
- const img = this.imageElementRef.nativeElement;
9661
- const container = this.previewDivRef.nativeElement;
9662
- const imgWidth = img.naturalWidth;
9663
- const imgHeight = img.naturalHeight;
9664
- const containerWidth = container.offsetWidth;
9665
- const containerHeight = container.offsetHeight;
9666
- const constrainToContainer = this.settingFocalPoint;
9667
- if (constrainToContainer) {
9668
- const controlsMarginPx = 48 * 2;
9669
- const availableHeight = containerHeight - controlsMarginPx;
9670
- const availableWidth = containerWidth;
9671
- const hRatio = imgHeight / availableHeight;
9672
- const wRatio = imgWidth / availableWidth;
9673
- const imageExceedsAvailableDimensions = 1 < hRatio || 1 < wRatio;
9674
- if (imageExceedsAvailableDimensions) {
9675
- const factor = hRatio < wRatio ? wRatio : hRatio;
9676
- this.width = Math.round(imgWidth / factor);
9677
- this.height = Math.round(imgHeight / factor);
9678
- this.centered = true;
9679
- return;
9680
- }
9834
+ // DragEvent is not supported in Safari, see https://github.com/vendure-ecommerce/vendure/pull/284
9835
+ onDragLeave(event) {
9836
+ if (!event.clientX && !event.clientY) {
9837
+ this.dragging = false;
9681
9838
  }
9682
- this.width = imgWidth;
9683
- this.height = imgHeight;
9684
- this.centered = imgWidth <= containerWidth && imgHeight <= containerHeight;
9685
- }
9686
- setFocalPointStart() {
9687
- this.sizePriorToSettingFocalPoint = this.size;
9688
- this.size = 'medium';
9689
- this.settingFocalPoint = true;
9690
- this.lastFocalPoint = this.asset.focalPoint || { x: 0.5, y: 0.5 };
9691
- this.updateDimensions();
9692
- }
9693
- removeFocalPoint() {
9694
- this.dataService.product
9695
- .updateAsset({
9696
- id: this.asset.id,
9697
- focalPoint: null,
9698
- })
9699
- .subscribe(() => {
9700
- this.notificationService.success(marker('asset.update-focal-point-success'));
9701
- this.asset = Object.assign(Object.assign({}, this.asset), { focalPoint: null });
9702
- this.changeDetector.markForCheck();
9703
- }, () => this.notificationService.error(marker('asset.update-focal-point-error')));
9704
9839
  }
9705
- onFocalPointChange(point) {
9706
- this.lastFocalPoint = point;
9840
+ /**
9841
+ * Preventing this event is required to make dropping work.
9842
+ * See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API#Define_a_drop_zone
9843
+ */
9844
+ onDragOver(event) {
9845
+ event.preventDefault();
9707
9846
  }
9708
- setFocalPointCancel() {
9709
- this.settingFocalPoint = false;
9710
- this.lastFocalPoint = undefined;
9711
- this.size = this.sizePriorToSettingFocalPoint;
9847
+ // DragEvent is not supported in Safari, see https://github.com/vendure-ecommerce/vendure/pull/284
9848
+ onDrop(event) {
9849
+ event.preventDefault();
9850
+ this.dragging = false;
9851
+ this.overDropZone = false;
9852
+ const files = Array.from(event.dataTransfer ? event.dataTransfer.items : [])
9853
+ .map(i => i.getAsFile())
9854
+ .filter(notNullOrUndefined);
9855
+ this.selectFiles.emit(files);
9712
9856
  }
9713
- setFocalPointEnd() {
9714
- this.settingFocalPoint = false;
9715
- this.size = this.sizePriorToSettingFocalPoint;
9716
- if (this.lastFocalPoint) {
9717
- const { x, y } = this.lastFocalPoint;
9718
- this.lastFocalPoint = undefined;
9719
- this.dataService.product
9720
- .updateAsset({
9721
- id: this.asset.id,
9722
- focalPoint: { x, y },
9723
- })
9724
- .subscribe(() => {
9725
- this.notificationService.success(marker('asset.update-focal-point-success'));
9726
- this.asset = Object.assign(Object.assign({}, this.asset), { focalPoint: { x, y } });
9727
- this.changeDetector.markForCheck();
9728
- }, () => this.notificationService.error(marker('asset.update-focal-point-error')));
9857
+ select(event) {
9858
+ const files = event.target.files;
9859
+ if (files) {
9860
+ this.selectFiles.emit(Array.from(files));
9729
9861
  }
9730
9862
  }
9731
- manageTags() {
9732
- this.modalService
9733
- .fromComponent(ManageTagsDialogComponent, {
9734
- size: 'sm',
9735
- })
9736
- .subscribe(result => {
9737
- if (result) {
9738
- this.notificationService.success(marker('common.notify-updated-tags-success'));
9739
- }
9740
- });
9863
+ fitDropZoneToTarget() {
9864
+ const target = document.querySelector(this.dropZoneTarget);
9865
+ if (target) {
9866
+ const rect = target.getBoundingClientRect();
9867
+ this.dropZoneStyle['width.px'] = rect.width;
9868
+ this.dropZoneStyle['height.px'] = rect.height;
9869
+ this.dropZoneStyle['top.px'] = rect.top;
9870
+ this.dropZoneStyle['left.px'] = rect.left;
9871
+ }
9741
9872
  }
9742
9873
  }
9743
- AssetPreviewComponent.decorators = [
9874
+ AssetFileInputComponent.decorators = [
9744
9875
  { type: Component, args: [{
9745
- selector: 'vdr-asset-preview',
9746
- template: "<div class=\"preview-image\" #previewDiv [class.centered]=\"centered\">\r\n <div class=\"image-wrapper\">\r\n <vdr-focal-point-control\r\n [width]=\"width\"\r\n [height]=\"height\"\r\n [fpx]=\"fpx\"\r\n [fpy]=\"fpy\"\r\n [editable]=\"settingFocalPoint\"\r\n (focalPointChange)=\"onFocalPointChange($event)\"\r\n >\r\n <img\r\n class=\"asset-image\"\r\n [src]=\"asset | assetPreview: size\"\r\n [ngClass]=\"size\"\r\n #imageElement\r\n (load)=\"onImageLoad()\"\r\n />\r\n </vdr-focal-point-control>\r\n <div class=\"focal-point-info\" *ngIf=\"settingFocalPoint\">\r\n <button class=\"icon-button\" (click)=\"setFocalPointCancel()\">\r\n <clr-icon shape=\"times\"></clr-icon>\r\n </button>\r\n <button class=\"btn btn-primary btn-sm\" (click)=\"setFocalPointEnd()\" [disabled]=\"!lastFocalPoint\">\r\n <clr-icon shape=\"crosshairs\"></clr-icon>\r\n {{ 'asset.set-focal-point' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div class=\"controls\" [class.fade]=\"settingFocalPoint\">\r\n <form [formGroup]=\"form\">\r\n <clr-input-container class=\"name-input\" *ngIf=\"editable\">\r\n <label>{{ 'common.name' | translate }}</label>\r\n <input\r\n clrInput\r\n type=\"text\"\r\n formControlName=\"name\"\r\n [readonly]=\"!(['UpdateCatalog', 'UpdateAsset'] | hasPermission) || settingFocalPoint\"\r\n />\r\n </clr-input-container>\r\n\r\n <vdr-labeled-data [label]=\"'common.name' | translate\" *ngIf=\"!editable\">\r\n <span class=\"elide\">\r\n {{ asset.name }}\r\n </span>\r\n </vdr-labeled-data>\r\n\r\n <vdr-labeled-data [label]=\"'asset.source-file' | translate\">\r\n <a [href]=\"asset.source\" [title]=\"asset.source\" target=\"_blank\" class=\"elide source-link\">{{\r\n getSourceFileName()\r\n }}</a>\r\n </vdr-labeled-data>\r\n\r\n <vdr-labeled-data [label]=\"'asset.original-asset-size' | translate\">\r\n {{ asset.fileSize | filesize }}\r\n </vdr-labeled-data>\r\n\r\n <vdr-labeled-data [label]=\"'asset.dimensions' | translate\">\r\n {{ asset.width }} x {{ asset.height }}\r\n </vdr-labeled-data>\r\n\r\n <vdr-labeled-data [label]=\"'asset.focal-point' | translate\">\r\n <span *ngIf=\"fpx\"\r\n ><clr-icon shape=\"crosshairs\"></clr-icon> x: {{ fpx | number: '1.2-2' }}, y:\r\n {{ fpy | number: '1.2-2' }}</span\r\n >\r\n <span *ngIf=\"!fpx\">{{ 'common.not-set' | translate }}</span>\r\n <br />\r\n <button\r\n class=\"btn btn-secondary-outline btn-sm\"\r\n [disabled]=\"settingFocalPoint\"\r\n (click)=\"setFocalPointStart()\"\r\n >\r\n <ng-container *ngIf=\"!fpx\">{{ 'asset.set-focal-point' | translate }}</ng-container>\r\n <ng-container *ngIf=\"fpx\">{{ 'asset.update-focal-point' | translate }}</ng-container>\r\n </button>\r\n <button\r\n class=\"btn btn-warning-outline btn-sm\"\r\n [disabled]=\"settingFocalPoint\"\r\n *ngIf=\"!!fpx\"\r\n (click)=\"removeFocalPoint()\"\r\n >\r\n {{ 'asset.unset-focal-point' | translate }}\r\n </button>\r\n </vdr-labeled-data>\r\n <vdr-labeled-data [label]=\"'common.tags' | translate\">\r\n <ng-container *ngIf=\"editable\">\r\n <vdr-tag-selector formControlName=\"tags\"></vdr-tag-selector>\r\n <button class=\"btn btn-link btn-sm\" (click)=\"manageTags()\">\r\n <clr-icon shape=\"tags\"></clr-icon>\r\n {{ 'common.manage-tags' | translate }}\r\n </button>\r\n </ng-container>\r\n <div *ngIf=\"!editable\">\r\n <vdr-chip *ngFor=\"let tag of asset.tags\" [colorFrom]=\"tag.value\">\r\n <clr-icon shape=\"tag\" class=\"mr2\"></clr-icon>\r\n {{ tag.value }}</vdr-chip\r\n >\r\n </div>\r\n </vdr-labeled-data>\r\n </form>\r\n <section *ngIf=\"customFields.length\">\r\n <label>{{ 'common.custom-fields' | translate }}</label>\r\n <vdr-tabbed-custom-fields\r\n entityName=\"Asset\"\r\n [compact]=\"true\"\r\n [customFields]=\"customFields\"\r\n [customFieldsFormGroup]=\"customFieldsForm\"\r\n [readonly]=\"!(['UpdateCatalog', 'UpdateAsset'] | hasPermission)\"\r\n ></vdr-tabbed-custom-fields>\r\n </section>\r\n <div class=\"flex-spacer\"></div>\r\n <div class=\"preview-select\">\r\n <clr-select-container>\r\n <label>{{ 'asset.preview' | translate }}</label>\r\n <select clrSelect name=\"options\" [(ngModel)]=\"size\" [disabled]=\"settingFocalPoint\">\r\n <option value=\"tiny\">tiny</option>\r\n <option value=\"thumb\">thumb</option>\r\n <option value=\"small\">small</option>\r\n <option value=\"medium\">medium</option>\r\n <option value=\"large\">large</option>\r\n <option value=\"\">full size</option>\r\n </select>\r\n </clr-select-container>\r\n <div class=\"asset-detail\">{{ width }} x {{ height }}</div>\r\n </div>\r\n <vdr-asset-preview-links class=\"mb4\" [asset]=\"asset\"></vdr-asset-preview-links>\r\n <div *ngIf=\"!editable\" class=\"edit-button-wrapper\">\r\n <a\r\n class=\"btn btn-link btn-sm\"\r\n [routerLink]=\"['/catalog', 'assets', asset.id]\"\r\n (click)=\"editClick.emit()\"\r\n >\r\n <clr-icon shape=\"edit\"></clr-icon>\r\n {{ 'common.edit' | translate }}\r\n </a>\r\n </div>\r\n</div>\r\n",
9876
+ selector: 'vdr-asset-file-input',
9877
+ template: "<input type=\"file\" class=\"file-input\" #fileInput (change)=\"select($event)\" multiple [accept]=\"accept\" />\r\n<button class=\"btn btn-primary\" (click)=\"fileInput.click()\" [disabled]=\"uploading\">\r\n <ng-container *ngIf=\"uploading; else selectable\">\r\n <clr-spinner clrInline></clr-spinner>\r\n {{ 'asset.uploading' | translate }}\r\n </ng-container>\r\n <ng-template #selectable>\r\n <clr-icon shape=\"upload-cloud\"></clr-icon>\r\n {{ 'asset.upload-assets' | translate }}\r\n </ng-template>\r\n</button>\r\n<div\r\n class=\"drop-zone\"\r\n [ngStyle]=\"dropZoneStyle\"\r\n [class.visible]=\"dragging\"\r\n [class.dragging-over]=\"overDropZone\"\r\n (dragenter)=\"overDropZone = true\"\r\n (dragleave)=\"overDropZone = false\"\r\n (dragover)=\"onDragOver($event)\"\r\n (drop)=\"onDrop($event)\"\r\n #dropZone\r\n>\r\n <div class=\"drop-label\" (dragenter)=\"overDropZone = true\">\r\n <clr-icon shape=\"upload-cloud\" size=\"32\"></clr-icon>\r\n {{ 'catalog.drop-files-to-upload' | translate }}\r\n </div>\r\n</div>\r\n",
9747
9878
  changeDetection: ChangeDetectionStrategy.OnPush,
9748
- styles: [":host{display:flex;height:100%}.preview-image{width:100%;height:100%;min-height:60vh;overflow:auto;text-align:center;box-shadow:inset 0 0 5px #0000001a;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACuoAAArqAVDM774AAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAAAK0lEQVQ4T2P4jwP8xgFGNSADqDwGIF0DlMYAUH0YYFQDMoDKYwASNfz/DwB/JvcficphowAAAABJRU5ErkJggg==);flex:1}.preview-image.centered{display:flex;align-items:center;justify-content:center}.preview-image vdr-focal-point-control{position:relative;box-shadow:0 0 10px -3px #00000026}.preview-image .image-wrapper{position:relative}.preview-image .asset-image{width:100%}.preview-image .asset-image.tiny{max-width:50px;max-height:50px}.preview-image .asset-image.thumb{max-width:150px;max-height:150px}.preview-image .asset-image.small{max-width:300px;max-height:300px}.preview-image .asset-image.medium{max-width:500px;max-height:500px}.preview-image .asset-image.large{max-width:800px;max-height:800px}.preview-image .focal-point-info{position:absolute;display:flex;right:0}.controls{display:flex;flex-direction:column;margin-left:12px;min-width:15vw;max-width:25vw;transition:opacity .3s}.controls.fade{opacity:.5}.controls .name-input{margin-bottom:24px}.controls ::ng-deep .clr-control-container{width:100%}.controls ::ng-deep .clr-control-container .clr-input{width:100%}.controls .elide{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;display:block}.controls .source-link{direction:rtl}.controls .preview-select{display:flex;align-items:center}.controls .preview-select clr-select-container{margin-right:12px}.edit-button-wrapper{padding-top:6px;border-top:1px solid var(--color-component-border-100);text-align:center}\n"]
9879
+ styles: [".file-input{display:none}.drop-zone{position:fixed;background-color:var(--color-primary-500);border:3px dashed var(--color-component-border-300);opacity:0;visibility:hidden;z-index:1000;transition:opacity .2s,background-color .2s,visibility 0s .2s;display:flex;align-items:center;justify-content:center}.drop-zone.visible{opacity:.3;visibility:visible;transition:opacity .2s,background-color .2s,border .2s,visibility 0s}.drop-zone .drop-label{background-color:#fffc;border-radius:3px;padding:24px;font-size:32px;pointer-events:none;opacity:.5;transition:opacity .2s}.drop-zone.dragging-over{border-color:#fff;background-color:var(--color-primary-500);opacity:.7;transition:background-color .2s,border .2s}.drop-zone.dragging-over .drop-label{opacity:1}\n"]
9749
9880
  },] }
9750
9881
  ];
9751
- AssetPreviewComponent.ctorParameters = () => [
9752
- { type: FormBuilder },
9753
- { type: DataService },
9754
- { type: NotificationService },
9755
- { type: ChangeDetectorRef },
9756
- { type: ModalService }
9882
+ AssetFileInputComponent.ctorParameters = () => [
9883
+ { type: ServerConfigService }
9757
9884
  ];
9758
- AssetPreviewComponent.propDecorators = {
9759
- asset: [{ type: Input }],
9760
- editable: [{ type: Input }],
9761
- customFields: [{ type: Input }],
9762
- customFieldsForm: [{ type: Input }],
9763
- assetChange: [{ type: Output }],
9764
- editClick: [{ type: Output }],
9765
- imageElementRef: [{ type: ViewChild, args: ['imageElement', { static: true },] }],
9766
- previewDivRef: [{ type: ViewChild, args: ['previewDiv', { static: true },] }]
9885
+ AssetFileInputComponent.propDecorators = {
9886
+ dropZoneTarget: [{ type: Input }],
9887
+ uploading: [{ type: Input }],
9888
+ selectFiles: [{ type: Output }],
9889
+ onDragEnter: [{ type: HostListener, args: ['document:dragenter',] }],
9890
+ onDragLeave: [{ type: HostListener, args: ['document:dragleave', ['$event'],] }]
9767
9891
  };
9768
9892
 
9769
- /**
9770
- * A custom SelectionModel for the NgSelect component which only allows a single
9771
- * search term at a time.
9772
- */
9773
- class SingleSearchSelectionModel {
9774
- constructor() {
9775
- this._selected = [];
9776
- }
9777
- get value() {
9778
- return this._selected;
9893
+ class AssetPreviewDialogComponent {
9894
+ constructor(dataService) {
9895
+ this.dataService = dataService;
9779
9896
  }
9780
- select(item, multiple, groupAsModel) {
9781
- item.selected = true;
9782
- if (groupAsModel || !item.children) {
9783
- if (item.value.label) {
9784
- const isSearchTerm = (i) => !!i.value.label;
9785
- const searchTerms = this._selected.filter(isSearchTerm);
9786
- if (searchTerms.length > 0) {
9787
- // there is already a search term, so replace it with this new one.
9788
- this._selected = this._selected.filter(i => !isSearchTerm(i)).concat(item);
9789
- }
9790
- else {
9791
- this._selected.push(item);
9792
- }
9897
+ ngOnInit() {
9898
+ this.assetWithTags$ = of(this.asset).pipe(mergeMap(asset => {
9899
+ if (this.hasTags(asset)) {
9900
+ return of(asset);
9793
9901
  }
9794
9902
  else {
9795
- this._selected.push(item);
9903
+ // tslint:disable-next-line:no-non-null-assertion
9904
+ return this.dataService.product.getAsset(asset.id).mapSingle(data => data.asset);
9905
+ }
9906
+ }));
9907
+ }
9908
+ hasTags(asset) {
9909
+ return asset.hasOwnProperty('tags');
9910
+ }
9911
+ }
9912
+ AssetPreviewDialogComponent.decorators = [
9913
+ { type: Component, args: [{
9914
+ selector: 'vdr-asset-preview-dialog',
9915
+ template: "<ng-template vdrDialogTitle>\r\n <div class=\"title-row\">\r\n {{ asset.name }}\r\n </div>\r\n</ng-template>\r\n\r\n<vdr-asset-preview\r\n *ngIf=\"assetWithTags$ | async as assetWithTags\"\r\n [asset]=\"assetWithTags\"\r\n (assetChange)=\"assetChanges = $event\"\r\n (editClick)=\"resolveWith()\"\r\n></vdr-asset-preview>\r\n",
9916
+ changeDetection: ChangeDetectionStrategy.OnPush,
9917
+ styles: [":host{height:70vh}.update-button.hidden{visibility:hidden}\n"]
9918
+ },] }
9919
+ ];
9920
+ AssetPreviewDialogComponent.ctorParameters = () => [
9921
+ { type: DataService }
9922
+ ];
9923
+
9924
+ class AssetGalleryComponent {
9925
+ constructor(modalService) {
9926
+ this.modalService = modalService;
9927
+ /**
9928
+ * If true, allows multiple assets to be selected by ctrl+clicking.
9929
+ */
9930
+ this.multiSelect = false;
9931
+ this.canDelete = false;
9932
+ this.selectionChange = new EventEmitter();
9933
+ this.deleteAssets = new EventEmitter();
9934
+ this.selectionManager = new SelectionManager({
9935
+ multiSelect: this.multiSelect,
9936
+ itemsAreEqual: (a, b) => a.id === b.id,
9937
+ additiveMode: false,
9938
+ });
9939
+ }
9940
+ ngOnChanges(changes) {
9941
+ if (this.assets) {
9942
+ for (const asset of this.selectionManager.selection) {
9943
+ // Update and selected assets with any changes
9944
+ const match = this.assets.find(a => a.id === asset.id);
9945
+ if (match) {
9946
+ Object.assign(asset, match);
9947
+ }
9796
9948
  }
9797
9949
  }
9950
+ if (changes['assets']) {
9951
+ this.selectionManager.setCurrentItems(this.assets);
9952
+ }
9953
+ if (changes['multiSelect']) {
9954
+ this.selectionManager.setMultiSelect(this.multiSelect);
9955
+ }
9798
9956
  }
9799
- unselect(item, multiple) {
9800
- this._selected = this._selected.filter(x => x !== item);
9801
- item.selected = false;
9957
+ toggleSelection(asset, event) {
9958
+ this.selectionManager.toggleSelection(asset, event);
9959
+ this.selectionChange.emit(this.selectionManager.selection);
9802
9960
  }
9803
- clear(keepDisabled) {
9804
- this._selected = keepDisabled ? this._selected.filter(x => x.disabled) : [];
9961
+ selectMultiple(assets) {
9962
+ this.selectionManager.selectMultiple(assets);
9963
+ this.selectionChange.emit(this.selectionManager.selection);
9805
9964
  }
9806
- _setChildrenSelectedState(children, selected) {
9807
- children.forEach(x => (x.selected = selected));
9965
+ isSelected(asset) {
9966
+ return this.selectionManager.isSelected(asset);
9808
9967
  }
9809
- _removeChildren(parent) {
9810
- this._selected = this._selected.filter(x => x.parent !== parent);
9968
+ lastSelected() {
9969
+ return this.selectionManager.lastSelected();
9811
9970
  }
9812
- _removeParent(parent) {
9813
- this._selected = this._selected.filter(x => x !== parent);
9971
+ previewAsset(asset) {
9972
+ this.modalService
9973
+ .fromComponent(AssetPreviewDialogComponent, {
9974
+ size: 'xl',
9975
+ closable: true,
9976
+ locals: { asset },
9977
+ })
9978
+ .subscribe();
9979
+ }
9980
+ entityInfoClick(event) {
9981
+ event.preventDefault();
9982
+ event.stopPropagation();
9814
9983
  }
9815
9984
  }
9816
- function SingleSearchSelectionModelFactory() {
9817
- return new SingleSearchSelectionModel();
9818
- }
9985
+ AssetGalleryComponent.decorators = [
9986
+ { type: Component, args: [{
9987
+ selector: 'vdr-asset-gallery',
9988
+ template: "<div class=\"gallery\">\r\n <div\r\n class=\"card\"\r\n *ngFor=\"let asset of assets\"\r\n (click)=\"toggleSelection(asset, $event)\"\r\n [class.selected]=\"isSelected(asset)\"\r\n >\r\n <div class=\"card-img\">\r\n <vdr-select-toggle\r\n [selected]=\"isSelected(asset)\"\r\n [disabled]=\"true\"\r\n [hiddenWhenOff]=\"true\"\r\n ></vdr-select-toggle>\r\n <img class=\"asset-thumb\" [src]=\"asset | assetPreview: 'thumb'\" />\r\n </div>\r\n <div class=\"detail\">\r\n <vdr-entity-info\r\n [entity]=\"asset\"\r\n [small]=\"true\"\r\n (click)=\"entityInfoClick($event)\"\r\n ></vdr-entity-info>\r\n <span [title]=\"asset.name\">{{ asset.name }}</span>\r\n </div>\r\n </div>\r\n</div>\r\n<div class=\"info-bar\">\r\n <div class=\"card\">\r\n <div class=\"card-img\">\r\n <div class=\"placeholder\" *ngIf=\"selectionManager.selection.length === 0\">\r\n <clr-icon shape=\"image\" size=\"128\"></clr-icon>\r\n <div>{{ 'catalog.no-selection' | translate }}</div>\r\n </div>\r\n <img\r\n class=\"preview\"\r\n *ngIf=\"selectionManager.selection.length >= 1\"\r\n [src]=\"lastSelected().preview + '?preset=medium'\"\r\n />\r\n </div>\r\n <div class=\"card-block details\" *ngIf=\"selectionManager.selection.length >= 1\">\r\n <div class=\"name\">{{ lastSelected().name }}</div>\r\n <div>{{ 'asset.original-asset-size' | translate }}: {{ lastSelected().fileSize | filesize }}</div>\r\n\r\n <ng-container *ngIf=\"selectionManager.selection.length === 1\">\r\n <vdr-chip *ngFor=\"let tag of lastSelected().tags\" [colorFrom]=\"tag.value\"\r\n ><clr-icon shape=\"tag\" class=\"mr2\"></clr-icon> {{ tag.value }}</vdr-chip\r\n >\r\n <div>\r\n <button (click)=\"previewAsset(lastSelected())\" class=\"btn btn-link\">\r\n <clr-icon shape=\"eye\"></clr-icon> {{ 'asset.preview' | translate }}\r\n </button>\r\n </div>\r\n <div>\r\n <vdr-asset-preview-links class=\"\" [asset]=\"lastSelected()\"></vdr-asset-preview-links>\r\n </div>\r\n <div>\r\n <a [routerLink]=\"['./', lastSelected().id]\" class=\"btn btn-link\">\r\n <clr-icon shape=\"pencil\"></clr-icon> {{ 'common.edit' | translate }}\r\n </a>\r\n </div>\r\n </ng-container>\r\n <div *ngIf=\"canDelete\">\r\n <button (click)=\"deleteAssets.emit(selectionManager.selection)\" class=\"btn btn-link\">\r\n <clr-icon shape=\"trash\" class=\"is-danger\"></clr-icon> {{ 'common.delete' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card stack\" [class.visible]=\"selectionManager.selection.length > 1\"></div>\r\n <div class=\"selection-count\" [class.visible]=\"selectionManager.selection.length > 1\">\r\n {{ 'asset.assets-selected-count' | translate: { count: selectionManager.selection.length } }}\r\n <ul>\r\n <li *ngFor=\"let asset of selectionManager.selection\">{{ asset.name }}</li>\r\n </ul>\r\n </div>\r\n</div>\r\n",
9989
+ changeDetection: ChangeDetectionStrategy.OnPush,
9990
+ styles: [":host{display:flex;overflow:hidden}.gallery{flex:1;display:grid;grid-template-columns:repeat(auto-fill,150px);grid-template-rows:repeat(auto-fill,180px);grid-gap:10px 20px;overflow-y:auto;padding-left:12px;padding-top:12px;padding-bottom:12px}.gallery .card:hover{box-shadow:0 .125rem 0 0 var(--color-primary-500);border:1px solid var(--color-primary-500)}.card{margin-top:0;position:relative}img.asset-thumb{aspect-ratio:1}vdr-select-toggle{position:absolute;top:-12px;left:-12px}vdr-select-toggle ::ng-deep .toggle{box-shadow:0 5px 5px -4px #000000bf}.card.selected{box-shadow:0 .125rem 0 0 var(--color-primary-500);border:1px solid var(--color-primary-500)}.card.selected .selected-checkbox{opacity:1}.detail{font-size:12px;margin:3px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.detail vdr-entity-info{height:16px}.info-bar{width:25%;padding:0 6px;overflow-y:auto}.info-bar .card{z-index:1}.info-bar .stack{z-index:0;opacity:0;transform:perspective(500px) translateZ(0) translateY(-16px);height:16px;transition:transform .3s,opacity 0s .3s;background-color:#fff}.info-bar .stack.visible{opacity:1;transform:perspective(500px) translateZ(-44px) translateY(0);background-color:var(--color-component-bg-100);transition:transform .3s,color .3s}.info-bar .selection-count{opacity:0;position:relative;text-align:center;visibility:hidden;transition:opacity .3s,visibility 0s .3s}.info-bar .selection-count.visible{opacity:1;visibility:visible;transition:opacity .3s,visibility 0s}.info-bar .selection-count ul{text-align:left;list-style-type:none;margin-left:12px}.info-bar .selection-count ul li{font-size:12px}.info-bar .placeholder{text-align:center;color:var(--color-grey-300)}.info-bar .preview img{max-width:100%}.info-bar .details{font-size:12px;word-break:break-all}.info-bar .name{line-height:14px;font-weight:bold}\n"]
9991
+ },] }
9992
+ ];
9993
+ AssetGalleryComponent.ctorParameters = () => [
9994
+ { type: ModalService }
9995
+ ];
9996
+ AssetGalleryComponent.propDecorators = {
9997
+ assets: [{ type: Input }],
9998
+ multiSelect: [{ type: Input }],
9999
+ canDelete: [{ type: Input }],
10000
+ selectionChange: [{ type: Output }],
10001
+ deleteAssets: [{ type: Output }]
10002
+ };
9819
10003
 
9820
- const ɵ0 = SingleSearchSelectionModelFactory;
9821
- class AssetSearchInputComponent {
9822
- constructor() {
9823
- this.searchTermChange = new EventEmitter();
9824
- this.tagsChange = new EventEmitter();
9825
- this.lastTerm = '';
9826
- this.lastTagIds = [];
9827
- this.filterTagResults = (term, item) => {
9828
- if (!this.isTag(item)) {
9829
- return false;
9830
- }
9831
- return item.value.toLowerCase().startsWith(term.toLowerCase());
9832
- };
9833
- this.isTag = (input) => {
9834
- return typeof input === 'object' && !!input && input.hasOwnProperty('value');
10004
+ /**
10005
+ * @description
10006
+ * A dialog which allows the creation and selection of assets.
10007
+ *
10008
+ * @example
10009
+ * ```TypeScript
10010
+ * selectAssets() {
10011
+ * this.modalService
10012
+ * .fromComponent(AssetPickerDialogComponent, {
10013
+ * size: 'xl',
10014
+ * })
10015
+ * .subscribe(result => {
10016
+ * if (result && result.length) {
10017
+ * // ...
10018
+ * }
10019
+ * });
10020
+ * }
10021
+ * ```
10022
+ *
10023
+ * @docsCategory components
10024
+ */
10025
+ class AssetPickerDialogComponent {
10026
+ constructor(dataService, notificationService) {
10027
+ this.dataService = dataService;
10028
+ this.notificationService = notificationService;
10029
+ this.paginationConfig = {
10030
+ currentPage: 1,
10031
+ itemsPerPage: 25,
10032
+ totalItems: 1,
9835
10033
  };
10034
+ this.multiSelect = true;
10035
+ this.initialTags = [];
10036
+ this.selection = [];
10037
+ this.searchTerm$ = new BehaviorSubject(undefined);
10038
+ this.filterByTags$ = new BehaviorSubject(undefined);
10039
+ this.uploading = false;
10040
+ this.destroy$ = new Subject();
9836
10041
  }
9837
- setSearchTerm(term) {
9838
- if (term) {
9839
- this.selectComponent.select({ label: term, value: { label: term } });
9840
- }
9841
- else {
9842
- const currentTerm = this.selectComponent.selectedItems.find(i => !this.isTag(i.value));
9843
- if (currentTerm) {
9844
- this.selectComponent.unselect(currentTerm);
9845
- }
9846
- }
9847
- }
9848
- setTags(tags) {
9849
- const items = this.selectComponent.items;
9850
- this.selectComponent.selectedItems.forEach(item => {
9851
- if (this.isTag(item.value) && !tags.map(t => t.id).includes(item.id)) {
9852
- this.selectComponent.unselect(item);
9853
- }
10042
+ ngOnInit() {
10043
+ this.listQuery = this.dataService.product.getAssetList(this.paginationConfig.itemsPerPage, 0);
10044
+ this.allTags$ = this.dataService.product.getTagList().mapSingle(data => data.tags.items);
10045
+ this.assets$ = this.listQuery.stream$.pipe(tap(result => (this.paginationConfig.totalItems = result.assets.totalItems)), map(result => result.assets.items));
10046
+ this.searchTerm$.pipe(debounceTime(250), takeUntil(this.destroy$)).subscribe(() => {
10047
+ this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9854
10048
  });
9855
- tags.map(tag => {
9856
- return items === null || items === void 0 ? void 0 : items.find(item => this.isTag(item) && item.id === tag.id);
9857
- })
9858
- .filter(notNullOrUndefined)
9859
- .forEach(item => {
9860
- const isSelected = this.selectComponent.selectedItems.find(i => {
9861
- const val = i.value;
9862
- if (this.isTag(val)) {
9863
- return val.id === item.id;
9864
- }
9865
- return false;
9866
- });
9867
- if (!isSelected) {
9868
- this.selectComponent.select({ label: '', value: item });
9869
- }
10049
+ this.filterByTags$.pipe(debounceTime(100), takeUntil(this.destroy$)).subscribe(() => {
10050
+ this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9870
10051
  });
9871
10052
  }
9872
- onSelectChange(selectedItems) {
9873
- if (!Array.isArray(selectedItems)) {
9874
- selectedItems = [selectedItems];
9875
- }
9876
- const searchTermItems = selectedItems.filter(item => !this.isTag(item));
9877
- if (1 < searchTermItems.length) {
9878
- for (let i = 0; i < searchTermItems.length - 1; i++) {
9879
- // this.selectComponent.unselect(searchTermItems[i] as any);
9880
- }
9881
- }
9882
- const searchTermItem = searchTermItems[searchTermItems.length - 1];
9883
- const searchTerm = searchTermItem ? searchTermItem.label : '';
9884
- const tags = selectedItems.filter(this.isTag);
9885
- if (searchTerm !== this.lastTerm) {
9886
- this.searchTermChange.emit(searchTerm);
9887
- this.lastTerm = searchTerm;
10053
+ ngAfterViewInit() {
10054
+ if (0 < this.initialTags.length) {
10055
+ this.allTags$
10056
+ .pipe(take(1), map(allTags => allTags.filter(tag => this.initialTags.includes(tag.value))), tap(tags => this.filterByTags$.next(tags)), delay(1))
10057
+ .subscribe(tags => this.assetSearchInputComponent.setTags(tags));
9888
10058
  }
9889
- if (this.lastTagIds.join(',') !== tags.map(t => t.id).join(',')) {
9890
- this.tagsChange.emit(tags);
9891
- this.lastTagIds = tags.map(t => t.id);
10059
+ }
10060
+ ngOnDestroy() {
10061
+ this.destroy$.next();
10062
+ this.destroy$.complete();
10063
+ }
10064
+ pageChange(page) {
10065
+ this.paginationConfig.currentPage = page;
10066
+ this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
10067
+ }
10068
+ itemsPerPageChange(itemsPerPage) {
10069
+ this.paginationConfig.itemsPerPage = itemsPerPage;
10070
+ this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
10071
+ }
10072
+ cancel() {
10073
+ this.resolveWith();
10074
+ }
10075
+ select() {
10076
+ this.resolveWith(this.selection);
10077
+ }
10078
+ createAssets(files) {
10079
+ if (files.length) {
10080
+ this.uploading = true;
10081
+ this.dataService.product
10082
+ .createAssets(files)
10083
+ .pipe(finalize(() => (this.uploading = false)))
10084
+ .subscribe(res => {
10085
+ this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
10086
+ this.notificationService.success(marker('asset.notify-create-assets-success'), {
10087
+ count: files.length,
10088
+ });
10089
+ const assets = res.createAssets.filter(a => a.__typename === 'Asset');
10090
+ this.assetGalleryComponent.selectMultiple(assets);
10091
+ });
9892
10092
  }
9893
10093
  }
9894
- isSearchHeaderSelected() {
9895
- return this.selectComponent.itemsList.markedIndex === -1;
10094
+ fetchPage(currentPage, itemsPerPage) {
10095
+ var _a;
10096
+ const take = +itemsPerPage;
10097
+ const skip = (currentPage - 1) * +itemsPerPage;
10098
+ const searchTerm = this.searchTerm$.value;
10099
+ const tags = (_a = this.filterByTags$.value) === null || _a === void 0 ? void 0 : _a.map(t => t.value);
10100
+ this.listQuery.ref.refetch({
10101
+ options: {
10102
+ skip,
10103
+ take,
10104
+ filter: {
10105
+ name: {
10106
+ contains: searchTerm,
10107
+ },
10108
+ },
10109
+ sort: {
10110
+ createdAt: SortOrder.DESC,
10111
+ },
10112
+ tags,
10113
+ tagsOperator: LogicalOperator.AND,
10114
+ },
10115
+ });
9896
10116
  }
9897
- addTagFn(item) {
9898
- return { label: item };
10117
+ }
10118
+ AssetPickerDialogComponent.decorators = [
10119
+ { type: Component, args: [{
10120
+ selector: 'vdr-asset-picker-dialog',
10121
+ template: "<ng-template vdrDialogTitle>\r\n <div class=\"title-row\">\r\n <span>{{ 'asset.select-assets' | translate }}</span>\r\n <div class=\"flex-spacer\"></div>\r\n <vdr-asset-file-input\r\n class=\"ml3\"\r\n (selectFiles)=\"createAssets($event)\"\r\n [uploading]=\"uploading\"\r\n dropZoneTarget=\".modal-content\"\r\n ></vdr-asset-file-input>\r\n </div>\r\n</ng-template>\r\n<vdr-asset-search-input\r\n class=\"mb2\"\r\n [tags]=\"allTags$ | async\"\r\n (searchTermChange)=\"searchTerm$.next($event)\"\r\n (tagsChange)=\"filterByTags$.next($event)\"\r\n #assetSearchInputComponent\r\n></vdr-asset-search-input>\r\n<vdr-asset-gallery\r\n [assets]=\"(assets$ | async)! | paginate: paginationConfig\"\r\n [multiSelect]=\"multiSelect\"\r\n (selectionChange)=\"selection = $event\"\r\n #assetGalleryComponent\r\n></vdr-asset-gallery>\r\n\r\n<div class=\"paging-controls\">\r\n <vdr-items-per-page-controls\r\n [itemsPerPage]=\"paginationConfig.itemsPerPage\"\r\n (itemsPerPageChange)=\"itemsPerPageChange($event)\"\r\n ></vdr-items-per-page-controls>\r\n\r\n <vdr-pagination-controls\r\n [currentPage]=\"paginationConfig.currentPage\"\r\n [itemsPerPage]=\"paginationConfig.itemsPerPage\"\r\n [totalItems]=\"paginationConfig.totalItems\"\r\n (pageChange)=\"pageChange($event)\"\r\n ></vdr-pagination-controls>\r\n</div>\r\n\r\n<ng-template vdrDialogButtons>\r\n <button type=\"button\" class=\"btn\" (click)=\"cancel()\">{{ 'common.cancel' | translate }}</button>\r\n <button type=\"submit\" (click)=\"select()\" class=\"btn btn-primary\" [disabled]=\"selection.length === 0\">\r\n {{ 'asset.add-asset-with-count' | translate: { count: selection.length } }}\r\n </button>\r\n</ng-template>\r\n",
10122
+ changeDetection: ChangeDetectionStrategy.OnPush,
10123
+ styles: [":host{display:flex;flex-direction:column;height:70vh}.title-row{display:flex;align-items:center;justify-content:space-between}vdr-asset-gallery{flex:1}.paging-controls{padding-top:6px;border-top:1px solid var(--color-component-border-100);display:flex;justify-content:space-between;flex-shrink:0}\n"]
10124
+ },] }
10125
+ ];
10126
+ AssetPickerDialogComponent.ctorParameters = () => [
10127
+ { type: DataService },
10128
+ { type: NotificationService }
10129
+ ];
10130
+ AssetPickerDialogComponent.propDecorators = {
10131
+ assetSearchInputComponent: [{ type: ViewChild, args: ['assetSearchInputComponent',] }],
10132
+ assetGalleryComponent: [{ type: ViewChild, args: ['assetGalleryComponent',] }]
10133
+ };
10134
+
10135
+ class AssetPreviewLinksComponent {
10136
+ constructor() {
10137
+ this.sizes = ['tiny', 'thumb', 'small', 'medium', 'large', 'full'];
9899
10138
  }
9900
10139
  }
9901
- AssetSearchInputComponent.decorators = [
10140
+ AssetPreviewLinksComponent.decorators = [
9902
10141
  { type: Component, args: [{
9903
- selector: 'vdr-asset-search-input',
9904
- template: "<ng-select\r\n [addTag]=\"addTagFn\"\r\n [placeholder]=\"'catalog.search-asset-name-or-tag' | translate\"\r\n [items]=\"tags\"\r\n [searchFn]=\"filterTagResults\"\r\n [hideSelected]=\"true\"\r\n [multiple]=\"true\"\r\n [markFirst]=\"false\"\r\n (change)=\"onSelectChange($event)\"\r\n #selectComponent\r\n>\r\n <ng-template ng-header-tmp>\r\n <div\r\n class=\"search-header\"\r\n *ngIf=\"selectComponent.searchTerm\"\r\n [class.selected]=\"isSearchHeaderSelected()\"\r\n (click)=\"selectComponent.selectTag()\"\r\n >\r\n {{ 'catalog.search-for-term' | translate }}: {{ selectComponent.searchTerm }}\r\n </div>\r\n </ng-template>\r\n <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\r\n <ng-container *ngIf=\"item.value\">\r\n <vdr-chip [colorFrom]=\"item.value\" icon=\"close\" (iconClick)=\"clear(item)\"><clr-icon shape=\"tag\" class=\"mr2\"></clr-icon> {{ item.value }}</vdr-chip>\r\n </ng-container>\r\n <ng-container *ngIf=\"!item.value\">\r\n <vdr-chip [icon]=\"'times'\" (iconClick)=\"clear(item)\">\"{{ item.label || item }}\"</vdr-chip>\r\n </ng-container>\r\n </ng-template>\r\n <ng-template ng-option-tmp let-item=\"item\" let-index=\"index\" let-search=\"searchTerm\">\r\n <ng-container *ngIf=\"item.value\">\r\n <vdr-chip [colorFrom]=\"item.value\"><clr-icon shape=\"tag\" class=\"mr2\"></clr-icon> {{ item.value }}</vdr-chip>\r\n </ng-container>\r\n </ng-template>\r\n</ng-select>\r\n",
10142
+ selector: 'vdr-asset-preview-links',
10143
+ template: "<vdr-dropdown>\r\n <button class=\"btn btn-link\" vdrDropdownTrigger>\r\n <clr-icon shape=\"link\"></clr-icon> {{ 'catalog.asset-preview-links' | translate }}<clr-icon shape=\"caret\" dir=\"down\"></clr-icon>\r\n </button>\r\n <vdr-dropdown-menu vdrPosition=\"bottom-left\">\r\n <a\r\n *ngFor=\"let size of sizes\"\r\n [href]=\"asset | assetPreview: size\"\r\n [title]=\"asset | assetPreview: size\"\r\n target=\"_blank\"\r\n class=\"asset-preview-link\"\r\n vdrDropdownItem\r\n >\r\n <vdr-chip><clr-icon shape=\"link\"></clr-icon> {{ 'asset.preview' | translate }}: {{ size }}</vdr-chip>\r\n </a>\r\n </vdr-dropdown-menu></vdr-dropdown\r\n>\r\n",
9905
10144
  changeDetection: ChangeDetectionStrategy.OnPush,
9906
- providers: [{ provide: SELECTION_MODEL_FACTORY, useValue: ɵ0 }],
9907
- styles: [":host{display:block;width:100%}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value{background:none;margin:0}:host ::ng-deep .ng-dropdown-panel-items div.ng-option:last-child{display:none}:host ::ng-deep .ng-dropdown-panel .ng-dropdown-header{border:none;padding:0}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container{padding:0}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder{padding-left:8px}ng-select{width:100%;min-width:300px;margin-right:12px}.search-header{padding:8px 10px;border-bottom:1px solid var(--color-component-border-100);cursor:pointer}.search-header.selected,.search-header:hover{background-color:var(--color-component-bg-200)}\n"]
10145
+ styles: [".asset-preview-link{font-size:12px}\n"]
9908
10146
  },] }
9909
10147
  ];
9910
- AssetSearchInputComponent.propDecorators = {
9911
- tags: [{ type: Input }],
9912
- searchTermChange: [{ type: Output }],
9913
- tagsChange: [{ type: Output }],
9914
- selectComponent: [{ type: ViewChild, args: ['selectComponent', { static: true },] }]
10148
+ AssetPreviewLinksComponent.propDecorators = {
10149
+ asset: [{ type: Input }]
9915
10150
  };
9916
10151
 
9917
- class ChannelAssignmentControlComponent {
10152
+ class ManageTagsDialogComponent {
9918
10153
  constructor(dataService) {
9919
10154
  this.dataService = dataService;
9920
- this.multiple = true;
9921
- this.includeDefaultChannel = true;
9922
- this.disableChannelIds = [];
9923
- this.value = [];
9924
- this.disabled = false;
10155
+ this.toDelete = [];
10156
+ this.toUpdate = [];
9925
10157
  }
9926
10158
  ngOnInit() {
9927
- this.channels$ = this.dataService.client.userStatus().single$.pipe(map(({ userStatus }) => userStatus.channels.filter(c => this.includeDefaultChannel ? true : c.code !== DEFAULT_CHANNEL_CODE)), tap(channels => {
9928
- if (!this.channels) {
9929
- this.channels = channels;
9930
- this.mapIncomingValueToChannels(this.lastIncomingValue);
9931
- }
9932
- else {
9933
- this.channels = channels;
9934
- }
9935
- }));
9936
- }
9937
- registerOnChange(fn) {
9938
- this.onChange = fn;
9939
- }
9940
- registerOnTouched(fn) {
9941
- this.onTouched = fn;
9942
- }
9943
- setDisabledState(isDisabled) {
9944
- this.disabled = isDisabled;
10159
+ this.allTags$ = this.dataService.product.getTagList().mapStream(data => data.tags.items);
9945
10160
  }
9946
- writeValue(obj) {
9947
- this.lastIncomingValue = obj;
9948
- this.mapIncomingValueToChannels(obj);
9949
- }
9950
- focussed() {
9951
- if (this.onTouched) {
9952
- this.onTouched();
10161
+ toggleDelete(id) {
10162
+ const marked = this.markedAsDeleted(id);
10163
+ if (marked) {
10164
+ this.toDelete = this.toDelete.filter(_id => _id !== id);
10165
+ }
10166
+ else {
10167
+ this.toDelete.push(id);
9953
10168
  }
9954
10169
  }
9955
- channelIsDisabled(id) {
9956
- return this.disableChannelIds.includes(id);
10170
+ markedAsDeleted(id) {
10171
+ return this.toDelete.includes(id);
9957
10172
  }
9958
- valueChanged(value) {
9959
- if (Array.isArray(value)) {
9960
- this.onChange(value.map(c => c.id));
10173
+ updateTagValue(id, value) {
10174
+ const exists = this.toUpdate.find(i => i.id === id);
10175
+ if (exists) {
10176
+ exists.value = value;
9961
10177
  }
9962
10178
  else {
9963
- this.onChange([value ? value.id : undefined]);
10179
+ this.toUpdate.push({ id, value });
9964
10180
  }
9965
10181
  }
9966
- compareFn(c1, c2) {
9967
- const c1id = typeof c1 === 'string' ? c1 : c1.id;
9968
- const c2id = typeof c2 === 'string' ? c2 : c2.id;
9969
- return c1id === c2id;
9970
- }
9971
- mapIncomingValueToChannels(value) {
9972
- var _a;
9973
- if (Array.isArray(value)) {
9974
- if (typeof value[0] === 'string') {
9975
- this.value = value
9976
- .map(id => { var _a; return (_a = this.channels) === null || _a === void 0 ? void 0 : _a.find(c => c.id === id); })
9977
- .filter(notNullOrUndefined);
9978
- }
9979
- else {
9980
- this.value = value;
9981
- }
10182
+ saveChanges() {
10183
+ const operations = [];
10184
+ for (const id of this.toDelete) {
10185
+ operations.push(this.dataService.product.deleteTag(id));
9982
10186
  }
9983
- else {
9984
- if (typeof value === 'string') {
9985
- const channel = (_a = this.channels) === null || _a === void 0 ? void 0 : _a.find(c => c.id === value);
9986
- if (channel) {
9987
- this.value = [channel];
9988
- }
9989
- }
9990
- else if (value && value.id) {
9991
- this.value = [value];
10187
+ for (const item of this.toUpdate) {
10188
+ if (!this.toDelete.includes(item.id)) {
10189
+ operations.push(this.dataService.product.updateTag(item));
9992
10190
  }
9993
10191
  }
10192
+ return forkJoin(operations).subscribe(() => this.resolveWith(true));
9994
10193
  }
9995
10194
  }
9996
- ChannelAssignmentControlComponent.decorators = [
10195
+ ManageTagsDialogComponent.decorators = [
9997
10196
  { type: Component, args: [{
9998
- selector: 'vdr-channel-assignment-control',
9999
- template: "<ng-select\r\n appendTo=\"body\"\r\n [addTag]=\"false\"\r\n [multiple]=\"multiple\"\r\n [ngModel]=\"value\"\r\n [clearable]=\"false\"\r\n [searchable]=\"false\"\r\n [disabled]=\"disabled\"\r\n [compareWith]=\"compareFn\"\r\n (focus)=\"focussed()\"\r\n (change)=\"valueChanged($event)\"\r\n>\r\n <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\r\n <span aria-hidden=\"true\" class=\"ng-value-icon left\" (click)=\"clear(item)\"> \u00D7 </span>\r\n <vdr-channel-badge [channelCode]=\"item.code\"></vdr-channel-badge>\r\n <span class=\"channel-label\">{{ item.code | channelCodeToLabel | translate }}</span>\r\n </ng-template>\r\n <ng-option *ngFor=\"let item of channels$ | async\" [value]=\"item\" [disabled]=\"channelIsDisabled(item.id)\">\r\n <vdr-channel-badge [channelCode]=\"item.code\"></vdr-channel-badge>\r\n {{ item.code | channelCodeToLabel | translate }}\r\n </ng-option>\r\n</ng-select>\r\n\r\n",
10000
- changeDetection: ChangeDetectionStrategy.Default,
10001
- providers: [
10002
- {
10003
- provide: NG_VALUE_ACCESSOR,
10004
- useExisting: ChannelAssignmentControlComponent,
10005
- multi: true,
10006
- },
10007
- ],
10008
- styles: [":host{min-width:200px}:host.clr-input{border-bottom:none;padding:0}::ng-deep .ng-value>vdr-channel-badge,::ng-deep .ng-option>vdr-channel-badge{margin-bottom:-1px}::ng-deep .ng-value>vdr-channel-badge{margin-left:6px}.channel-label{margin-right:6px}\n"]
10197
+ selector: 'vdr-manage-tags-dialog',
10198
+ template: "<ng-template vdrDialogTitle>\r\n <span>{{ 'common.manage-tags' | translate }}</span>\r\n</ng-template>\r\n<p class=\"mt0 mb4\">{{ 'common.manage-tags-description' | translate }}</p>\r\n<ul class=\"tag-list\" *ngFor=\"let tag of allTags$ | async\">\r\n <li class=\"mb2 p1\" [class.to-delete]=\"markedAsDeleted(tag.id)\">\r\n <clr-icon shape=\"tag\" class=\"is-solid mr2\" [style.color]=\"tag.value | stringToColor\"></clr-icon>\r\n <input type=\"text\" (input)=\"updateTagValue(tag.id, $event.target.value)\" [value]=\"tag.value\" />\r\n <button class=\"icon-button\" (click)=\"toggleDelete(tag.id)\">\r\n <clr-icon shape=\"trash\" class=\"is-danger\" [class.is-solid]=\"markedAsDeleted(tag.id)\"></clr-icon>\r\n </button>\r\n </li>\r\n</ul>\r\n<ng-template vdrDialogButtons>\r\n <button type=\"submit\" (click)=\"resolveWith(false)\" class=\"btn btn-secondary\">\r\n {{ 'common.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"submit\"\r\n (click)=\"saveChanges()\"\r\n class=\"btn btn-primary\"\r\n [disabled]=\"!toUpdate.length && !toDelete.length\"\r\n >\r\n {{ 'common.update' | translate }}\r\n </button>\r\n</ng-template>\r\n",
10199
+ changeDetection: ChangeDetectionStrategy.OnPush,
10200
+ styles: [".tag-list{list-style-type:none}.tag-list li{display:flex;align-items:center}.tag-list li input{max-width:170px}.tag-list li.to-delete{opacity:.7;background-color:var(--color-component-bg-300)}.tag-list li.to-delete input{background-color:transparent!important}\n"]
10009
10201
  },] }
10010
10202
  ];
10011
- ChannelAssignmentControlComponent.ctorParameters = () => [
10203
+ ManageTagsDialogComponent.ctorParameters = () => [
10012
10204
  { type: DataService }
10013
- ];
10014
- ChannelAssignmentControlComponent.propDecorators = {
10015
- multiple: [{ type: Input }],
10016
- includeDefaultChannel: [{ type: Input }],
10017
- disableChannelIds: [{ type: Input }]
10018
- };
10205
+ ];
10019
10206
 
10020
- class ChannelBadgeComponent {
10021
- get isDefaultChannel() {
10022
- return this.channelCode === DEFAULT_CHANNEL_CODE;
10207
+ class AssetPreviewComponent {
10208
+ constructor(formBuilder, dataService, notificationService, changeDetector, modalService) {
10209
+ this.formBuilder = formBuilder;
10210
+ this.dataService = dataService;
10211
+ this.notificationService = notificationService;
10212
+ this.changeDetector = changeDetector;
10213
+ this.modalService = modalService;
10214
+ this.editable = false;
10215
+ this.customFields = [];
10216
+ this.assetChange = new EventEmitter();
10217
+ this.editClick = new EventEmitter();
10218
+ this.size = 'medium';
10219
+ this.width = 0;
10220
+ this.height = 0;
10221
+ this.centered = true;
10222
+ this.settingFocalPoint = false;
10223
+ }
10224
+ get fpx() {
10225
+ return this.asset.focalPoint ? this.asset.focalPoint.x : null;
10226
+ }
10227
+ get fpy() {
10228
+ return this.asset.focalPoint ? this.asset.focalPoint.y : null;
10229
+ }
10230
+ ngOnInit() {
10231
+ var _a;
10232
+ const { focalPoint } = this.asset;
10233
+ this.form = this.formBuilder.group({
10234
+ name: [this.asset.name],
10235
+ tags: [(_a = this.asset.tags) === null || _a === void 0 ? void 0 : _a.map(t => t.value)],
10236
+ });
10237
+ this.subscription = this.form.valueChanges.subscribe(value => {
10238
+ this.assetChange.emit({
10239
+ id: this.asset.id,
10240
+ name: value.name,
10241
+ tags: value.tags,
10242
+ });
10243
+ });
10244
+ this.subscription.add(fromEvent(window, 'resize')
10245
+ .pipe(debounceTime(50))
10246
+ .subscribe(() => {
10247
+ this.updateDimensions();
10248
+ this.changeDetector.markForCheck();
10249
+ }));
10250
+ }
10251
+ ngOnDestroy() {
10252
+ if (this.subscription) {
10253
+ this.subscription.unsubscribe();
10254
+ }
10255
+ }
10256
+ getSourceFileName() {
10257
+ const parts = this.asset.source.split(/[\\\/]/g);
10258
+ return parts[parts.length - 1];
10259
+ }
10260
+ onImageLoad() {
10261
+ this.updateDimensions();
10262
+ this.changeDetector.markForCheck();
10263
+ }
10264
+ updateDimensions() {
10265
+ const img = this.imageElementRef.nativeElement;
10266
+ const container = this.previewDivRef.nativeElement;
10267
+ const imgWidth = img.naturalWidth;
10268
+ const imgHeight = img.naturalHeight;
10269
+ const containerWidth = container.offsetWidth;
10270
+ const containerHeight = container.offsetHeight;
10271
+ const constrainToContainer = this.settingFocalPoint;
10272
+ if (constrainToContainer) {
10273
+ const controlsMarginPx = 48 * 2;
10274
+ const availableHeight = containerHeight - controlsMarginPx;
10275
+ const availableWidth = containerWidth;
10276
+ const hRatio = imgHeight / availableHeight;
10277
+ const wRatio = imgWidth / availableWidth;
10278
+ const imageExceedsAvailableDimensions = 1 < hRatio || 1 < wRatio;
10279
+ if (imageExceedsAvailableDimensions) {
10280
+ const factor = hRatio < wRatio ? wRatio : hRatio;
10281
+ this.width = Math.round(imgWidth / factor);
10282
+ this.height = Math.round(imgHeight / factor);
10283
+ this.centered = true;
10284
+ return;
10285
+ }
10286
+ }
10287
+ this.width = imgWidth;
10288
+ this.height = imgHeight;
10289
+ this.centered = imgWidth <= containerWidth && imgHeight <= containerHeight;
10290
+ }
10291
+ setFocalPointStart() {
10292
+ this.sizePriorToSettingFocalPoint = this.size;
10293
+ this.size = 'medium';
10294
+ this.settingFocalPoint = true;
10295
+ this.lastFocalPoint = this.asset.focalPoint || { x: 0.5, y: 0.5 };
10296
+ this.updateDimensions();
10297
+ }
10298
+ removeFocalPoint() {
10299
+ this.dataService.product
10300
+ .updateAsset({
10301
+ id: this.asset.id,
10302
+ focalPoint: null,
10303
+ })
10304
+ .subscribe(() => {
10305
+ this.notificationService.success(marker('asset.update-focal-point-success'));
10306
+ this.asset = Object.assign(Object.assign({}, this.asset), { focalPoint: null });
10307
+ this.changeDetector.markForCheck();
10308
+ }, () => this.notificationService.error(marker('asset.update-focal-point-error')));
10309
+ }
10310
+ onFocalPointChange(point) {
10311
+ this.lastFocalPoint = point;
10312
+ }
10313
+ setFocalPointCancel() {
10314
+ this.settingFocalPoint = false;
10315
+ this.lastFocalPoint = undefined;
10316
+ this.size = this.sizePriorToSettingFocalPoint;
10317
+ }
10318
+ setFocalPointEnd() {
10319
+ this.settingFocalPoint = false;
10320
+ this.size = this.sizePriorToSettingFocalPoint;
10321
+ if (this.lastFocalPoint) {
10322
+ const { x, y } = this.lastFocalPoint;
10323
+ this.lastFocalPoint = undefined;
10324
+ this.dataService.product
10325
+ .updateAsset({
10326
+ id: this.asset.id,
10327
+ focalPoint: { x, y },
10328
+ })
10329
+ .subscribe(() => {
10330
+ this.notificationService.success(marker('asset.update-focal-point-success'));
10331
+ this.asset = Object.assign(Object.assign({}, this.asset), { focalPoint: { x, y } });
10332
+ this.changeDetector.markForCheck();
10333
+ }, () => this.notificationService.error(marker('asset.update-focal-point-error')));
10334
+ }
10335
+ }
10336
+ manageTags() {
10337
+ this.modalService
10338
+ .fromComponent(ManageTagsDialogComponent, {
10339
+ size: 'sm',
10340
+ })
10341
+ .subscribe(result => {
10342
+ if (result) {
10343
+ this.notificationService.success(marker('common.notify-updated-tags-success'));
10344
+ }
10345
+ });
10023
10346
  }
10024
10347
  }
10025
- ChannelBadgeComponent.decorators = [
10348
+ AssetPreviewComponent.decorators = [
10026
10349
  { type: Component, args: [{
10027
- selector: 'vdr-channel-badge',
10028
- template: "<clr-icon shape=\"layers\" [style.color]=\"isDefaultChannel ? '#aaa' : (channelCode | stringToColor)\"></clr-icon>\r\n",
10350
+ selector: 'vdr-asset-preview',
10351
+ template: "<div class=\"preview-image\" #previewDiv [class.centered]=\"centered\">\r\n <div class=\"image-wrapper\">\r\n <vdr-focal-point-control\r\n [width]=\"width\"\r\n [height]=\"height\"\r\n [fpx]=\"fpx\"\r\n [fpy]=\"fpy\"\r\n [editable]=\"settingFocalPoint\"\r\n (focalPointChange)=\"onFocalPointChange($event)\"\r\n >\r\n <img\r\n class=\"asset-image\"\r\n [src]=\"asset | assetPreview: size\"\r\n [ngClass]=\"size\"\r\n #imageElement\r\n (load)=\"onImageLoad()\"\r\n />\r\n </vdr-focal-point-control>\r\n <div class=\"focal-point-info\" *ngIf=\"settingFocalPoint\">\r\n <button class=\"icon-button\" (click)=\"setFocalPointCancel()\">\r\n <clr-icon shape=\"times\"></clr-icon>\r\n </button>\r\n <button class=\"btn btn-primary btn-sm\" (click)=\"setFocalPointEnd()\" [disabled]=\"!lastFocalPoint\">\r\n <clr-icon shape=\"crosshairs\"></clr-icon>\r\n {{ 'asset.set-focal-point' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div class=\"controls\" [class.fade]=\"settingFocalPoint\">\r\n <form [formGroup]=\"form\">\r\n <clr-input-container class=\"name-input\" *ngIf=\"editable\">\r\n <label>{{ 'common.name' | translate }}</label>\r\n <input\r\n clrInput\r\n type=\"text\"\r\n formControlName=\"name\"\r\n [readonly]=\"!(['UpdateCatalog', 'UpdateAsset'] | hasPermission) || settingFocalPoint\"\r\n />\r\n </clr-input-container>\r\n\r\n <vdr-labeled-data [label]=\"'common.name' | translate\" *ngIf=\"!editable\">\r\n <span class=\"elide\">\r\n {{ asset.name }}\r\n </span>\r\n </vdr-labeled-data>\r\n\r\n <vdr-labeled-data [label]=\"'asset.source-file' | translate\">\r\n <a [href]=\"asset.source\" [title]=\"asset.source\" target=\"_blank\" class=\"elide source-link\">{{\r\n getSourceFileName()\r\n }}</a>\r\n </vdr-labeled-data>\r\n\r\n <vdr-labeled-data [label]=\"'asset.original-asset-size' | translate\">\r\n {{ asset.fileSize | filesize }}\r\n </vdr-labeled-data>\r\n\r\n <vdr-labeled-data [label]=\"'asset.dimensions' | translate\">\r\n {{ asset.width }} x {{ asset.height }}\r\n </vdr-labeled-data>\r\n\r\n <vdr-labeled-data [label]=\"'asset.focal-point' | translate\">\r\n <span *ngIf=\"fpx\"\r\n ><clr-icon shape=\"crosshairs\"></clr-icon> x: {{ fpx | number: '1.2-2' }}, y:\r\n {{ fpy | number: '1.2-2' }}</span\r\n >\r\n <span *ngIf=\"!fpx\">{{ 'common.not-set' | translate }}</span>\r\n <br />\r\n <button\r\n class=\"btn btn-secondary-outline btn-sm\"\r\n [disabled]=\"settingFocalPoint\"\r\n (click)=\"setFocalPointStart()\"\r\n >\r\n <ng-container *ngIf=\"!fpx\">{{ 'asset.set-focal-point' | translate }}</ng-container>\r\n <ng-container *ngIf=\"fpx\">{{ 'asset.update-focal-point' | translate }}</ng-container>\r\n </button>\r\n <button\r\n class=\"btn btn-warning-outline btn-sm\"\r\n [disabled]=\"settingFocalPoint\"\r\n *ngIf=\"!!fpx\"\r\n (click)=\"removeFocalPoint()\"\r\n >\r\n {{ 'asset.unset-focal-point' | translate }}\r\n </button>\r\n </vdr-labeled-data>\r\n <vdr-labeled-data [label]=\"'common.tags' | translate\">\r\n <ng-container *ngIf=\"editable\">\r\n <vdr-tag-selector formControlName=\"tags\"></vdr-tag-selector>\r\n <button class=\"btn btn-link btn-sm\" (click)=\"manageTags()\">\r\n <clr-icon shape=\"tags\"></clr-icon>\r\n {{ 'common.manage-tags' | translate }}\r\n </button>\r\n </ng-container>\r\n <div *ngIf=\"!editable\">\r\n <vdr-chip *ngFor=\"let tag of asset.tags\" [colorFrom]=\"tag.value\">\r\n <clr-icon shape=\"tag\" class=\"mr2\"></clr-icon>\r\n {{ tag.value }}</vdr-chip\r\n >\r\n </div>\r\n </vdr-labeled-data>\r\n </form>\r\n <section *ngIf=\"customFields.length\">\r\n <label>{{ 'common.custom-fields' | translate }}</label>\r\n <vdr-tabbed-custom-fields\r\n entityName=\"Asset\"\r\n [compact]=\"true\"\r\n [customFields]=\"customFields\"\r\n [customFieldsFormGroup]=\"customFieldsForm\"\r\n [readonly]=\"!(['UpdateCatalog', 'UpdateAsset'] | hasPermission)\"\r\n ></vdr-tabbed-custom-fields>\r\n </section>\r\n <div class=\"flex-spacer\"></div>\r\n <div class=\"preview-select\">\r\n <clr-select-container>\r\n <label>{{ 'asset.preview' | translate }}</label>\r\n <select clrSelect name=\"options\" [(ngModel)]=\"size\" [disabled]=\"settingFocalPoint\">\r\n <option value=\"tiny\">tiny</option>\r\n <option value=\"thumb\">thumb</option>\r\n <option value=\"small\">small</option>\r\n <option value=\"medium\">medium</option>\r\n <option value=\"large\">large</option>\r\n <option value=\"\">full size</option>\r\n </select>\r\n </clr-select-container>\r\n <div class=\"asset-detail\">{{ width }} x {{ height }}</div>\r\n </div>\r\n <vdr-asset-preview-links class=\"mb4\" [asset]=\"asset\"></vdr-asset-preview-links>\r\n <div *ngIf=\"!editable\" class=\"edit-button-wrapper\">\r\n <a\r\n class=\"btn btn-link btn-sm\"\r\n [routerLink]=\"['/catalog', 'assets', asset.id]\"\r\n (click)=\"editClick.emit()\"\r\n >\r\n <clr-icon shape=\"edit\"></clr-icon>\r\n {{ 'common.edit' | translate }}\r\n </a>\r\n </div>\r\n</div>\r\n",
10029
10352
  changeDetection: ChangeDetectionStrategy.OnPush,
10030
- styles: [":host{display:inline-block}button :host{margin-bottom:-1px}clr-icon{margin-right:6px}\n"]
10353
+ styles: [":host{display:flex;height:100%}.preview-image{width:100%;height:100%;min-height:60vh;overflow:auto;text-align:center;box-shadow:inset 0 0 5px #0000001a;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACuoAAArqAVDM774AAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAAAK0lEQVQ4T2P4jwP8xgFGNSADqDwGIF0DlMYAUH0YYFQDMoDKYwASNfz/DwB/JvcficphowAAAABJRU5ErkJggg==);flex:1}.preview-image.centered{display:flex;align-items:center;justify-content:center}.preview-image vdr-focal-point-control{position:relative;box-shadow:0 0 10px -3px #00000026}.preview-image .image-wrapper{position:relative}.preview-image .asset-image{width:100%}.preview-image .asset-image.tiny{max-width:50px;max-height:50px}.preview-image .asset-image.thumb{max-width:150px;max-height:150px}.preview-image .asset-image.small{max-width:300px;max-height:300px}.preview-image .asset-image.medium{max-width:500px;max-height:500px}.preview-image .asset-image.large{max-width:800px;max-height:800px}.preview-image .focal-point-info{position:absolute;display:flex;right:0}.controls{display:flex;flex-direction:column;margin-left:12px;min-width:15vw;max-width:25vw;transition:opacity .3s}.controls.fade{opacity:.5}.controls .name-input{margin-bottom:24px}.controls ::ng-deep .clr-control-container{width:100%}.controls ::ng-deep .clr-control-container .clr-input{width:100%}.controls .elide{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;display:block}.controls .source-link{direction:rtl}.controls .preview-select{display:flex;align-items:center}.controls .preview-select clr-select-container{margin-right:12px}.edit-button-wrapper{padding-top:6px;border-top:1px solid var(--color-component-border-100);text-align:center}\n"]
10031
10354
  },] }
10032
10355
  ];
10033
- ChannelBadgeComponent.propDecorators = {
10034
- channelCode: [{ type: Input }]
10356
+ AssetPreviewComponent.ctorParameters = () => [
10357
+ { type: FormBuilder },
10358
+ { type: DataService },
10359
+ { type: NotificationService },
10360
+ { type: ChangeDetectorRef },
10361
+ { type: ModalService }
10362
+ ];
10363
+ AssetPreviewComponent.propDecorators = {
10364
+ asset: [{ type: Input }],
10365
+ editable: [{ type: Input }],
10366
+ customFields: [{ type: Input }],
10367
+ customFieldsForm: [{ type: Input }],
10368
+ assetChange: [{ type: Output }],
10369
+ editClick: [{ type: Output }],
10370
+ imageElementRef: [{ type: ViewChild, args: ['imageElement', { static: true },] }],
10371
+ previewDivRef: [{ type: ViewChild, args: ['previewDiv', { static: true },] }]
10035
10372
  };
10036
10373
 
10037
10374
  /**
10038
- * @description
10039
- * A chip component for displaying a label with an optional action icon.
10040
- *
10041
- * @example
10042
- * ```HTML
10043
- * <vdr-chip [colorFrom]="item.value"
10044
- * icon="close"
10045
- * (iconClick)="clear(item)">
10046
- * {{ item.value }}</vdr-chip>
10047
- * ```
10048
- * @docsCategory components
10375
+ * A custom SelectionModel for the NgSelect component which only allows a single
10376
+ * search term at a time.
10049
10377
  */
10050
- class ChipComponent {
10378
+ class SingleSearchSelectionModel {
10051
10379
  constructor() {
10052
- this.invert = false;
10053
- /**
10054
- * @description
10055
- * If set, the chip will have an auto-generated background
10056
- * color based on the string value passed in.
10057
- */
10058
- this.colorFrom = '';
10059
- this.iconClick = new EventEmitter();
10060
- }
10061
- }
10062
- ChipComponent.decorators = [
10063
- { type: Component, args: [{
10064
- selector: 'vdr-chip',
10065
- template: "<div\r\n class=\"wrapper\"\r\n [class.with-background]=\"!invert && colorFrom\"\r\n [style.backgroundColor]=\"!invert && (colorFrom | stringToColor)\"\r\n [style.color]=\"invert && (colorFrom | stringToColor)\"\r\n [style.borderColor]=\"invert && (colorFrom | stringToColor)\"\r\n [ngClass]=\"colorType\"\r\n>\r\n <div class=\"chip-label\"><ng-content></ng-content></div>\r\n <div class=\"chip-icon\" *ngIf=\"icon\">\r\n <button (click)=\"iconClick.emit($event)\">\r\n <clr-icon\r\n [attr.shape]=\"icon\"\r\n [style.color]=\"invert && (colorFrom | stringToColor)\"\r\n [class.is-inverse]=\"!invert && colorFrom\"\r\n ></clr-icon>\r\n </button>\r\n </div>\r\n</div>\r\n",
10066
- changeDetection: ChangeDetectionStrategy.OnPush,
10067
- styles: [":host{display:inline-block}.wrapper{display:flex;border:1px solid var(--color-component-border-300);border-radius:3px;margin:6px}.wrapper.with-background{color:var(--color-grey-100);border-color:transparent}.wrapper.with-background .chip-label{opacity:.9}.wrapper.warning{border-color:var(--color-chip-warning-border)}.wrapper.warning .chip-label{color:var(--color-chip-warning-text);background-color:var(--color-chip-warning-bg)}.wrapper.success{border-color:var(--color-chip-success-border)}.wrapper.success .chip-label{color:var(--color-chip-success-text);background-color:var(--color-chip-success-bg)}.wrapper.error{border-color:var(--color-chip-error-border)}.wrapper.error .chip-label{color:var(--color-chip-error-text);background-color:var(--color-chip-error-bg)}.chip-label{padding:3px 6px;line-height:1em;border-radius:3px;white-space:nowrap;display:flex;align-items:center}.chip-icon{border-left:1px solid var(--color-component-border-200);padding:0 3px;line-height:1em;display:flex}.chip-icon button{cursor:pointer;background:none;margin:0;padding:0;border:none}\n"]
10068
- },] }
10069
- ];
10070
- ChipComponent.propDecorators = {
10071
- icon: [{ type: Input }],
10072
- invert: [{ type: Input }],
10073
- colorFrom: [{ type: Input }],
10074
- colorType: [{ type: Input }],
10075
- iconClick: [{ type: Output }]
10076
- };
10077
-
10078
- /**
10079
- * ConfigArg values are always stored as strings. If they are not primitives, then
10080
- * they are JSON-encoded. This function unwraps them back into their original
10081
- * data type.
10082
- */
10083
- function getConfigArgValue(value) {
10084
- try {
10085
- return value ? JSON.parse(value) : undefined;
10086
- }
10087
- catch (e) {
10088
- return value;
10089
- }
10090
- }
10091
- function encodeConfigArgValue(value) {
10092
- return Array.isArray(value) ? JSON.stringify(value) : (value !== null && value !== void 0 ? value : '').toString();
10093
- }
10094
- /**
10095
- * Creates an empty ConfigurableOperation object based on the definition.
10096
- */
10097
- function configurableDefinitionToInstance(def) {
10098
- return Object.assign(Object.assign({}, def), { args: def.args.map(arg => {
10099
- return Object.assign(Object.assign({}, arg), { value: getDefaultConfigArgValue(arg) });
10100
- }) });
10101
- }
10102
- /**
10103
- * Converts an object of the type:
10104
- * ```
10105
- * {
10106
- * code: 'my-operation',
10107
- * args: {
10108
- * someProperty: 'foo'
10109
- * }
10110
- * }
10111
- * ```
10112
- * to the format defined by the ConfigurableOperationInput GraphQL input type:
10113
- * ```
10114
- * {
10115
- * code: 'my-operation',
10116
- * args: [
10117
- * { name: 'someProperty', value: 'foo' }
10118
- * ]
10119
- * }
10120
- * ```
10121
- */
10122
- function toConfigurableOperationInput(operation, formValueOperations) {
10123
- return {
10124
- code: operation.code,
10125
- arguments: Object.values(formValueOperations.args || {}).map((value, j) => ({
10126
- name: operation.args[j].name,
10127
- value: value.hasOwnProperty('value')
10128
- ? encodeConfigArgValue(value.value)
10129
- : encodeConfigArgValue(value),
10130
- })),
10131
- };
10132
- }
10133
- function configurableOperationValueIsValid(def, value) {
10134
- if (!def || !value) {
10135
- return false;
10380
+ this._selected = [];
10136
10381
  }
10137
- if (def.code !== value.code) {
10138
- return false;
10382
+ get value() {
10383
+ return this._selected;
10139
10384
  }
10140
- for (const argDef of def.args) {
10141
- const argVal = value.args[argDef.name];
10142
- if (argDef.required && (argVal == null || argVal === '' || argVal === '0')) {
10143
- return false;
10385
+ select(item, multiple, groupAsModel) {
10386
+ item.selected = true;
10387
+ if (groupAsModel || !item.children) {
10388
+ if (item.value.label) {
10389
+ const isSearchTerm = (i) => !!i.value.label;
10390
+ const searchTerms = this._selected.filter(isSearchTerm);
10391
+ if (searchTerms.length > 0) {
10392
+ // there is already a search term, so replace it with this new one.
10393
+ this._selected = this._selected.filter(i => !isSearchTerm(i)).concat(item);
10394
+ }
10395
+ else {
10396
+ this._selected.push(item);
10397
+ }
10398
+ }
10399
+ else {
10400
+ this._selected.push(item);
10401
+ }
10144
10402
  }
10145
10403
  }
10146
- return true;
10147
- }
10148
- /**
10149
- * Returns a default value based on the type of the config arg.
10150
- */
10151
- function getDefaultConfigArgValue(arg) {
10152
- if (arg.list) {
10153
- return [];
10404
+ unselect(item, multiple) {
10405
+ this._selected = this._selected.filter(x => x !== item);
10406
+ item.selected = false;
10154
10407
  }
10155
- if (arg.defaultValue) {
10156
- return arg.defaultValue;
10408
+ clear(keepDisabled) {
10409
+ this._selected = keepDisabled ? this._selected.filter(x => x.disabled) : [];
10157
10410
  }
10158
- const type = arg.type;
10159
- switch (type) {
10160
- case 'string':
10161
- case 'datetime':
10162
- case 'float':
10163
- case 'ID':
10164
- case 'int':
10165
- return null;
10166
- case 'boolean':
10167
- return false;
10168
- default:
10169
- assertNever(type);
10411
+ _setChildrenSelectedState(children, selected) {
10412
+ children.forEach(x => (x.selected = selected));
10170
10413
  }
10171
- }
10172
-
10173
- /**
10174
- * Interpolates the description of an ConfigurableOperation with the given values.
10175
- */
10176
- function interpolateDescription(operation, values) {
10177
- if (!operation) {
10178
- return '';
10414
+ _removeChildren(parent) {
10415
+ this._selected = this._selected.filter(x => x.parent !== parent);
10179
10416
  }
10180
- const templateString = operation.description;
10181
- const interpolated = templateString.replace(/{\s*([a-zA-Z0-9]+)\s*}/gi, (substring, argName) => {
10182
- const normalizedArgName = argName.toLowerCase();
10183
- const value = values[normalizedArgName];
10184
- if (value == null) {
10185
- return '_';
10186
- }
10187
- let formatted = value;
10188
- const argDef = operation.args.find(arg => arg.name === normalizedArgName);
10189
- if (argDef && argDef.type === 'int' && argDef.ui && argDef.ui.component === 'currency-form-input') {
10190
- formatted = value / 100;
10191
- }
10192
- if (argDef && argDef.type === 'datetime' && value instanceof Date) {
10193
- formatted = value.toLocaleDateString();
10194
- }
10195
- return formatted;
10196
- });
10197
- return interpolated;
10417
+ _removeParent(parent) {
10418
+ this._selected = this._selected.filter(x => x !== parent);
10419
+ }
10420
+ }
10421
+ function SingleSearchSelectionModelFactory() {
10422
+ return new SingleSearchSelectionModel();
10198
10423
  }
10199
10424
 
10200
- /**
10201
- * A form input which renders a card with the internal form fields of the given ConfigurableOperation.
10202
- */
10203
- class ConfigurableInputComponent {
10425
+ const ɵ0$1 = SingleSearchSelectionModelFactory;
10426
+ class AssetSearchInputComponent {
10204
10427
  constructor() {
10205
- this.readonly = false;
10206
- this.removable = true;
10207
- this.remove = new EventEmitter();
10208
- this.argValues = {};
10209
- this.form = new FormGroup({});
10428
+ this.searchTermChange = new EventEmitter();
10429
+ this.tagsChange = new EventEmitter();
10430
+ this.lastTerm = '';
10431
+ this.lastTagIds = [];
10432
+ this.filterTagResults = (term, item) => {
10433
+ if (!this.isTag(item)) {
10434
+ return false;
10435
+ }
10436
+ return item.value.toLowerCase().startsWith(term.toLowerCase());
10437
+ };
10438
+ this.isTag = (input) => {
10439
+ return typeof input === 'object' && !!input && input.hasOwnProperty('value');
10440
+ };
10210
10441
  }
10211
- interpolateDescription() {
10212
- if (this.operationDefinition) {
10213
- return interpolateDescription(this.operationDefinition, this.form.value);
10442
+ setSearchTerm(term) {
10443
+ if (term) {
10444
+ this.selectComponent.select({ label: term, value: { label: term } });
10214
10445
  }
10215
10446
  else {
10216
- return '';
10447
+ const currentTerm = this.selectComponent.selectedItems.find(i => !this.isTag(i.value));
10448
+ if (currentTerm) {
10449
+ this.selectComponent.unselect(currentTerm);
10450
+ }
10217
10451
  }
10218
10452
  }
10219
- ngOnChanges(changes) {
10220
- if ('operation' in changes || 'operationDefinition' in changes) {
10221
- this.createForm();
10222
- }
10453
+ setTags(tags) {
10454
+ const items = this.selectComponent.items;
10455
+ this.selectComponent.selectedItems.forEach(item => {
10456
+ if (this.isTag(item.value) && !tags.map(t => t.id).includes(item.id)) {
10457
+ this.selectComponent.unselect(item);
10458
+ }
10459
+ });
10460
+ tags.map(tag => {
10461
+ return items === null || items === void 0 ? void 0 : items.find(item => this.isTag(item) && item.id === tag.id);
10462
+ })
10463
+ .filter(notNullOrUndefined)
10464
+ .forEach(item => {
10465
+ const isSelected = this.selectComponent.selectedItems.find(i => {
10466
+ const val = i.value;
10467
+ if (this.isTag(val)) {
10468
+ return val.id === item.id;
10469
+ }
10470
+ return false;
10471
+ });
10472
+ if (!isSelected) {
10473
+ this.selectComponent.select({ label: '', value: item });
10474
+ }
10475
+ });
10223
10476
  }
10224
- ngOnDestroy() {
10225
- if (this.subscription) {
10226
- this.subscription.unsubscribe();
10477
+ onSelectChange(selectedItems) {
10478
+ if (!Array.isArray(selectedItems)) {
10479
+ selectedItems = [selectedItems];
10227
10480
  }
10481
+ const searchTermItems = selectedItems.filter(item => !this.isTag(item));
10482
+ if (1 < searchTermItems.length) {
10483
+ for (let i = 0; i < searchTermItems.length - 1; i++) {
10484
+ // this.selectComponent.unselect(searchTermItems[i] as any);
10485
+ }
10486
+ }
10487
+ const searchTermItem = searchTermItems[searchTermItems.length - 1];
10488
+ const searchTerm = searchTermItem ? searchTermItem.label : '';
10489
+ const tags = selectedItems.filter(this.isTag);
10490
+ if (searchTerm !== this.lastTerm) {
10491
+ this.searchTermChange.emit(searchTerm);
10492
+ this.lastTerm = searchTerm;
10493
+ }
10494
+ if (this.lastTagIds.join(',') !== tags.map(t => t.id).join(',')) {
10495
+ this.tagsChange.emit(tags);
10496
+ this.lastTagIds = tags.map(t => t.id);
10497
+ }
10498
+ }
10499
+ isSearchHeaderSelected() {
10500
+ return this.selectComponent.itemsList.markedIndex === -1;
10501
+ }
10502
+ addTagFn(item) {
10503
+ return { label: item };
10504
+ }
10505
+ }
10506
+ AssetSearchInputComponent.decorators = [
10507
+ { type: Component, args: [{
10508
+ selector: 'vdr-asset-search-input',
10509
+ template: "<ng-select\r\n [addTag]=\"addTagFn\"\r\n [placeholder]=\"'catalog.search-asset-name-or-tag' | translate\"\r\n [items]=\"tags\"\r\n [searchFn]=\"filterTagResults\"\r\n [hideSelected]=\"true\"\r\n [multiple]=\"true\"\r\n [markFirst]=\"false\"\r\n (change)=\"onSelectChange($event)\"\r\n #selectComponent\r\n>\r\n <ng-template ng-header-tmp>\r\n <div\r\n class=\"search-header\"\r\n *ngIf=\"selectComponent.searchTerm\"\r\n [class.selected]=\"isSearchHeaderSelected()\"\r\n (click)=\"selectComponent.selectTag()\"\r\n >\r\n {{ 'catalog.search-for-term' | translate }}: {{ selectComponent.searchTerm }}\r\n </div>\r\n </ng-template>\r\n <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\r\n <ng-container *ngIf=\"item.value\">\r\n <vdr-chip [colorFrom]=\"item.value\" icon=\"close\" (iconClick)=\"clear(item)\"><clr-icon shape=\"tag\" class=\"mr2\"></clr-icon> {{ item.value }}</vdr-chip>\r\n </ng-container>\r\n <ng-container *ngIf=\"!item.value\">\r\n <vdr-chip [icon]=\"'times'\" (iconClick)=\"clear(item)\">\"{{ item.label || item }}\"</vdr-chip>\r\n </ng-container>\r\n </ng-template>\r\n <ng-template ng-option-tmp let-item=\"item\" let-index=\"index\" let-search=\"searchTerm\">\r\n <ng-container *ngIf=\"item.value\">\r\n <vdr-chip [colorFrom]=\"item.value\"><clr-icon shape=\"tag\" class=\"mr2\"></clr-icon> {{ item.value }}</vdr-chip>\r\n </ng-container>\r\n </ng-template>\r\n</ng-select>\r\n",
10510
+ changeDetection: ChangeDetectionStrategy.OnPush,
10511
+ providers: [{ provide: SELECTION_MODEL_FACTORY, useValue: ɵ0$1 }],
10512
+ styles: [":host{display:block;width:100%}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value{background:none;margin:0}:host ::ng-deep .ng-dropdown-panel-items div.ng-option:last-child{display:none}:host ::ng-deep .ng-dropdown-panel .ng-dropdown-header{border:none;padding:0}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container{padding:0}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder{padding-left:8px}ng-select{width:100%;min-width:300px;margin-right:12px}.search-header{padding:8px 10px;border-bottom:1px solid var(--color-component-border-100);cursor:pointer}.search-header.selected,.search-header:hover{background-color:var(--color-component-bg-200)}\n"]
10513
+ },] }
10514
+ ];
10515
+ AssetSearchInputComponent.propDecorators = {
10516
+ tags: [{ type: Input }],
10517
+ searchTermChange: [{ type: Output }],
10518
+ tagsChange: [{ type: Output }],
10519
+ selectComponent: [{ type: ViewChild, args: ['selectComponent', { static: true },] }]
10520
+ };
10521
+
10522
+ class ChannelAssignmentControlComponent {
10523
+ constructor(dataService) {
10524
+ this.dataService = dataService;
10525
+ this.multiple = true;
10526
+ this.includeDefaultChannel = true;
10527
+ this.disableChannelIds = [];
10528
+ this.value = [];
10529
+ this.disabled = false;
10530
+ }
10531
+ ngOnInit() {
10532
+ this.channels$ = this.dataService.client.userStatus().single$.pipe(map(({ userStatus }) => userStatus.channels.filter(c => this.includeDefaultChannel ? true : c.code !== DEFAULT_CHANNEL_CODE)), tap(channels => {
10533
+ if (!this.channels) {
10534
+ this.channels = channels;
10535
+ this.mapIncomingValueToChannels(this.lastIncomingValue);
10536
+ }
10537
+ else {
10538
+ this.channels = channels;
10539
+ }
10540
+ }));
10228
10541
  }
10229
10542
  registerOnChange(fn) {
10230
10543
  this.onChange = fn;
10231
10544
  }
10232
10545
  registerOnTouched(fn) {
10233
- this.onTouch = fn;
10546
+ this.onTouched = fn;
10234
10547
  }
10235
10548
  setDisabledState(isDisabled) {
10236
- if (isDisabled) {
10237
- this.form.disable();
10238
- }
10239
- else {
10240
- this.form.enable();
10241
- }
10549
+ this.disabled = isDisabled;
10242
10550
  }
10243
- writeValue(value) {
10244
- if (value) {
10245
- this.form.patchValue(value);
10246
- }
10551
+ writeValue(obj) {
10552
+ this.lastIncomingValue = obj;
10553
+ this.mapIncomingValueToChannels(obj);
10247
10554
  }
10248
- trackByName(index, arg) {
10249
- return arg.name;
10555
+ focussed() {
10556
+ if (this.onTouched) {
10557
+ this.onTouched();
10558
+ }
10250
10559
  }
10251
- getArgDef(arg) {
10252
- var _a;
10253
- return (_a = this.operationDefinition) === null || _a === void 0 ? void 0 : _a.args.find(a => a.name === arg.name);
10560
+ channelIsDisabled(id) {
10561
+ return this.disableChannelIds.includes(id);
10254
10562
  }
10255
- createForm() {
10256
- var _a, _b;
10257
- if (!this.operation) {
10258
- return;
10563
+ valueChanged(value) {
10564
+ if (Array.isArray(value)) {
10565
+ this.onChange(value.map(c => c.id));
10259
10566
  }
10260
- if (this.subscription) {
10261
- this.subscription.unsubscribe();
10567
+ else {
10568
+ this.onChange([value ? value.id : undefined]);
10262
10569
  }
10263
- this.form = new FormGroup({});
10264
- this.form.__id = Math.random().toString(36).substr(10);
10265
- if (this.operation.args) {
10266
- for (const arg of ((_a = this.operationDefinition) === null || _a === void 0 ? void 0 : _a.args) || []) {
10267
- let value = (_b = this.operation.args.find(a => a.name === arg.name)) === null || _b === void 0 ? void 0 : _b.value;
10268
- if (value === undefined) {
10269
- value = getDefaultConfigArgValue(arg);
10270
- }
10271
- const validators = arg.list ? undefined : arg.required ? Validators.required : undefined;
10272
- this.form.addControl(arg.name, new FormControl(value, validators));
10570
+ }
10571
+ compareFn(c1, c2) {
10572
+ const c1id = typeof c1 === 'string' ? c1 : c1.id;
10573
+ const c2id = typeof c2 === 'string' ? c2 : c2.id;
10574
+ return c1id === c2id;
10575
+ }
10576
+ mapIncomingValueToChannels(value) {
10577
+ var _a;
10578
+ if (Array.isArray(value)) {
10579
+ if (typeof value[0] === 'string') {
10580
+ this.value = value
10581
+ .map(id => { var _a; return (_a = this.channels) === null || _a === void 0 ? void 0 : _a.find(c => c.id === id); })
10582
+ .filter(notNullOrUndefined);
10583
+ }
10584
+ else {
10585
+ this.value = value;
10273
10586
  }
10274
10587
  }
10275
- this.subscription = this.form.valueChanges.subscribe(value => {
10276
- if (this.onChange) {
10277
- this.onChange({
10278
- code: this.operation && this.operation.code,
10279
- args: value,
10280
- });
10588
+ else {
10589
+ if (typeof value === 'string') {
10590
+ const channel = (_a = this.channels) === null || _a === void 0 ? void 0 : _a.find(c => c.id === value);
10591
+ if (channel) {
10592
+ this.value = [channel];
10593
+ }
10281
10594
  }
10282
- if (this.onTouch) {
10283
- this.onTouch();
10595
+ else if (value && value.id) {
10596
+ this.value = [value];
10284
10597
  }
10285
- });
10286
- }
10287
- validate(c) {
10288
- if (this.form.invalid) {
10289
- return {
10290
- required: true,
10291
- };
10292
10598
  }
10293
- return null;
10294
10599
  }
10295
10600
  }
10296
- ConfigurableInputComponent.decorators = [
10601
+ ChannelAssignmentControlComponent.decorators = [
10297
10602
  { type: Component, args: [{
10298
- selector: 'vdr-configurable-input',
10299
- template: "<div class=\"card\" *ngIf=\"operation\">\r\n <div class=\"card-block\">{{ interpolateDescription() }}</div>\r\n <div class=\"card-block\" *ngIf=\"operation.args?.length\">\r\n <form [formGroup]=\"form\" *ngIf=\"operation\" class=\"operation-inputs\">\r\n <div *ngFor=\"let arg of operation.args; trackBy: trackByName\" class=\"arg-row\">\r\n <ng-container *ngIf=\"form.get(arg.name)\">\r\n <label>{{ getArgDef(arg)?.label || (arg.name | sentenceCase) }}</label>\r\n <vdr-dynamic-form-input\r\n [def]=\"getArgDef(arg)\"\r\n [readonly]=\"readonly\"\r\n [control]=\"form.get(arg.name)\"\r\n [formControlName]=\"arg.name\"\r\n ></vdr-dynamic-form-input>\r\n </ng-container>\r\n </div>\r\n </form>\r\n </div>\r\n <div class=\"card-footer\" *ngIf=\"!readonly && removable\">\r\n <button class=\"btn btn-sm btn-link btn-warning\" (click)=\"remove.emit(operation)\">\r\n <clr-icon shape=\"times\"></clr-icon>\r\n {{ 'common.remove' | translate }}\r\n </button>\r\n </div>\r\n</div>\r\n",
10300
- changeDetection: ChangeDetectionStrategy.OnPush,
10603
+ selector: 'vdr-channel-assignment-control',
10604
+ template: "<ng-select\r\n appendTo=\"body\"\r\n [addTag]=\"false\"\r\n [multiple]=\"multiple\"\r\n [ngModel]=\"value\"\r\n [clearable]=\"false\"\r\n [searchable]=\"false\"\r\n [disabled]=\"disabled\"\r\n [compareWith]=\"compareFn\"\r\n (focus)=\"focussed()\"\r\n (change)=\"valueChanged($event)\"\r\n>\r\n <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\r\n <span aria-hidden=\"true\" class=\"ng-value-icon left\" (click)=\"clear(item)\"> \u00D7 </span>\r\n <vdr-channel-badge [channelCode]=\"item.code\"></vdr-channel-badge>\r\n <span class=\"channel-label\">{{ item.code | channelCodeToLabel | translate }}</span>\r\n </ng-template>\r\n <ng-option *ngFor=\"let item of channels$ | async\" [value]=\"item\" [disabled]=\"channelIsDisabled(item.id)\">\r\n <vdr-channel-badge [channelCode]=\"item.code\"></vdr-channel-badge>\r\n {{ item.code | channelCodeToLabel | translate }}\r\n </ng-option>\r\n</ng-select>\r\n\r\n",
10605
+ changeDetection: ChangeDetectionStrategy.Default,
10301
10606
  providers: [
10302
10607
  {
10303
10608
  provide: NG_VALUE_ACCESSOR,
10304
- useExisting: ConfigurableInputComponent,
10305
- multi: true,
10306
- },
10307
- {
10308
- provide: NG_VALIDATORS,
10309
- useExisting: forwardRef(() => ConfigurableInputComponent),
10609
+ useExisting: ChannelAssignmentControlComponent,
10310
10610
  multi: true,
10311
10611
  },
10312
10612
  ],
10313
- styles: [":host{display:block;margin-bottom:12px}:host>.card{margin-top:6px}.operation-inputs{padding-top:0}.operation-inputs .arg-row:not(:last-child){margin-bottom:24px}.operation-inputs .arg-row label{margin-right:6px}.operation-inputs .hidden{display:none}\n"]
10613
+ styles: [":host{min-width:200px}:host.clr-input{border-bottom:none;padding:0}::ng-deep .ng-value>vdr-channel-badge,::ng-deep .ng-option>vdr-channel-badge{margin-bottom:-1px}::ng-deep .ng-value>vdr-channel-badge{margin-left:6px}.channel-label{margin-right:6px}\n"]
10314
10614
  },] }
10315
10615
  ];
10316
- ConfigurableInputComponent.propDecorators = {
10317
- operation: [{ type: Input }],
10318
- operationDefinition: [{ type: Input }],
10319
- readonly: [{ type: Input }],
10320
- removable: [{ type: Input }],
10321
- remove: [{ type: Output }]
10616
+ ChannelAssignmentControlComponent.ctorParameters = () => [
10617
+ { type: DataService }
10618
+ ];
10619
+ ChannelAssignmentControlComponent.propDecorators = {
10620
+ multiple: [{ type: Input }],
10621
+ includeDefaultChannel: [{ type: Input }],
10622
+ disableChannelIds: [{ type: Input }]
10623
+ };
10624
+
10625
+ class ChannelBadgeComponent {
10626
+ get isDefaultChannel() {
10627
+ return this.channelCode === DEFAULT_CHANNEL_CODE;
10628
+ }
10629
+ }
10630
+ ChannelBadgeComponent.decorators = [
10631
+ { type: Component, args: [{
10632
+ selector: 'vdr-channel-badge',
10633
+ template: "<clr-icon shape=\"layers\" [style.color]=\"isDefaultChannel ? '#aaa' : (channelCode | stringToColor)\"></clr-icon>\r\n",
10634
+ changeDetection: ChangeDetectionStrategy.OnPush,
10635
+ styles: [":host{display:inline-block}button :host{margin-bottom:-1px}clr-icon{margin-right:6px}\n"]
10636
+ },] }
10637
+ ];
10638
+ ChannelBadgeComponent.propDecorators = {
10639
+ channelCode: [{ type: Input }]
10640
+ };
10641
+
10642
+ /**
10643
+ * @description
10644
+ * A chip component for displaying a label with an optional action icon.
10645
+ *
10646
+ * @example
10647
+ * ```HTML
10648
+ * <vdr-chip [colorFrom]="item.value"
10649
+ * icon="close"
10650
+ * (iconClick)="clear(item)">
10651
+ * {{ item.value }}</vdr-chip>
10652
+ * ```
10653
+ * @docsCategory components
10654
+ */
10655
+ class ChipComponent {
10656
+ constructor() {
10657
+ this.invert = false;
10658
+ /**
10659
+ * @description
10660
+ * If set, the chip will have an auto-generated background
10661
+ * color based on the string value passed in.
10662
+ */
10663
+ this.colorFrom = '';
10664
+ this.iconClick = new EventEmitter();
10665
+ }
10666
+ }
10667
+ ChipComponent.decorators = [
10668
+ { type: Component, args: [{
10669
+ selector: 'vdr-chip',
10670
+ template: "<div\r\n class=\"wrapper\"\r\n [class.with-background]=\"!invert && colorFrom\"\r\n [style.backgroundColor]=\"!invert && (colorFrom | stringToColor)\"\r\n [style.color]=\"invert && (colorFrom | stringToColor)\"\r\n [style.borderColor]=\"invert && (colorFrom | stringToColor)\"\r\n [ngClass]=\"colorType\"\r\n>\r\n <div class=\"chip-label\"><ng-content></ng-content></div>\r\n <div class=\"chip-icon\" *ngIf=\"icon\">\r\n <button (click)=\"iconClick.emit($event)\">\r\n <clr-icon\r\n [attr.shape]=\"icon\"\r\n [style.color]=\"invert && (colorFrom | stringToColor)\"\r\n [class.is-inverse]=\"!invert && colorFrom\"\r\n ></clr-icon>\r\n </button>\r\n </div>\r\n</div>\r\n",
10671
+ changeDetection: ChangeDetectionStrategy.OnPush,
10672
+ styles: [":host{display:inline-block}.wrapper{display:flex;border:1px solid var(--color-component-border-300);border-radius:3px;margin:6px}.wrapper.with-background{color:var(--color-grey-100);border-color:transparent}.wrapper.with-background .chip-label{opacity:.9}.wrapper.warning{border-color:var(--color-chip-warning-border)}.wrapper.warning .chip-label{color:var(--color-chip-warning-text);background-color:var(--color-chip-warning-bg)}.wrapper.success{border-color:var(--color-chip-success-border)}.wrapper.success .chip-label{color:var(--color-chip-success-text);background-color:var(--color-chip-success-bg)}.wrapper.error{border-color:var(--color-chip-error-border)}.wrapper.error .chip-label{color:var(--color-chip-error-text);background-color:var(--color-chip-error-bg)}.chip-label{padding:3px 6px;line-height:1em;border-radius:3px;white-space:nowrap;display:flex;align-items:center}.chip-icon{border-left:1px solid var(--color-component-border-200);padding:0 3px;line-height:1em;display:flex}.chip-icon button{cursor:pointer;background:none;margin:0;padding:0;border:none}\n"]
10673
+ },] }
10674
+ ];
10675
+ ChipComponent.propDecorators = {
10676
+ icon: [{ type: Input }],
10677
+ invert: [{ type: Input }],
10678
+ colorFrom: [{ type: Input }],
10679
+ colorType: [{ type: Input }],
10680
+ iconClick: [{ type: Output }]
10322
10681
  };
10323
10682
 
10324
10683
  /**
@@ -11941,7 +12300,7 @@ HelpTooltipComponent.decorators = [
11941
12300
  selector: 'vdr-help-tooltip',
11942
12301
  template: "<clr-tooltip>\r\n <clr-icon clrTooltipTrigger shape=\"help\" size=\"14\"></clr-icon>\r\n <clr-tooltip-content [clrPosition]=\"position\" clrSize=\"md\" *clrIfOpen>\r\n <span>{{ content }}</span>\r\n </clr-tooltip-content>\r\n</clr-tooltip>\r\n",
11943
12302
  changeDetection: ChangeDetectionStrategy.OnPush,
11944
- styles: [""]
12303
+ styles: ["clr-tooltip{display:inline-flex}\n"]
11945
12304
  },] }
11946
12305
  ];
11947
12306
  HelpTooltipComponent.propDecorators = {
@@ -12203,6 +12562,105 @@ PaginationControlsComponent.propDecorators = {
12203
12562
  pageChange: [{ type: Output }]
12204
12563
  };
12205
12564
 
12565
+ const ɵ0 = SingleSearchSelectionModelFactory;
12566
+ class ProductSearchInputComponent {
12567
+ constructor() {
12568
+ this.searchTermChange = new EventEmitter();
12569
+ this.facetValueChange = new EventEmitter();
12570
+ this.lastTerm = '';
12571
+ this.lastFacetValueIds = [];
12572
+ this.filterFacetResults = (term, item) => {
12573
+ if (!this.isFacetValueItem(item)) {
12574
+ return false;
12575
+ }
12576
+ const cix = term.indexOf(':');
12577
+ const facetName = cix > -1 ? term.toLowerCase().slice(0, cix) : null;
12578
+ const facetVal = cix > -1 ? term.toLowerCase().slice(cix + 1) : term.toLowerCase();
12579
+ if (facetName) {
12580
+ return (item.facetValue.facet.name.toLowerCase().includes(facetName) &&
12581
+ item.facetValue.name.toLocaleLowerCase().includes(facetVal));
12582
+ }
12583
+ return (item.facetValue.name.toLowerCase().includes(term.toLowerCase()) ||
12584
+ item.facetValue.facet.name.toLowerCase().includes(term.toLowerCase()));
12585
+ };
12586
+ this.isFacetValueItem = (input) => {
12587
+ return typeof input === 'object' && !!input && input.hasOwnProperty('facetValue');
12588
+ };
12589
+ }
12590
+ setSearchTerm(term) {
12591
+ if (term) {
12592
+ this.selectComponent.select({ label: term, value: { label: term } });
12593
+ }
12594
+ else {
12595
+ const currentTerm = this.selectComponent.selectedItems.find(i => !this.isFacetValueItem(i.value));
12596
+ if (currentTerm) {
12597
+ this.selectComponent.unselect(currentTerm);
12598
+ }
12599
+ }
12600
+ }
12601
+ setFacetValues(ids) {
12602
+ const items = this.selectComponent.items;
12603
+ this.selectComponent.selectedItems.forEach(item => {
12604
+ if (this.isFacetValueItem(item.value) && !ids.includes(item.value.facetValue.id)) {
12605
+ this.selectComponent.unselect(item);
12606
+ }
12607
+ });
12608
+ ids.map(id => {
12609
+ return items === null || items === void 0 ? void 0 : items.find(item => this.isFacetValueItem(item) && item.facetValue.id === id);
12610
+ })
12611
+ .filter(notNullOrUndefined)
12612
+ .forEach(item => {
12613
+ const isSelected = this.selectComponent.selectedItems.find(i => {
12614
+ const val = i.value;
12615
+ if (this.isFacetValueItem(val)) {
12616
+ return val.facetValue.id === item.facetValue.id;
12617
+ }
12618
+ return false;
12619
+ });
12620
+ if (!isSelected) {
12621
+ this.selectComponent.select({ label: '', value: item });
12622
+ }
12623
+ });
12624
+ }
12625
+ onSelectChange(selectedItems) {
12626
+ if (!Array.isArray(selectedItems)) {
12627
+ selectedItems = [selectedItems];
12628
+ }
12629
+ const searchTermItem = selectedItems.find(item => !this.isFacetValueItem(item));
12630
+ const searchTerm = searchTermItem ? searchTermItem.label : '';
12631
+ const facetValueIds = selectedItems.filter(this.isFacetValueItem).map(i => i.facetValue.id);
12632
+ if (searchTerm !== this.lastTerm) {
12633
+ this.searchTermChange.emit(searchTerm);
12634
+ this.lastTerm = searchTerm;
12635
+ }
12636
+ if (this.lastFacetValueIds.join(',') !== facetValueIds.join(',')) {
12637
+ this.facetValueChange.emit(facetValueIds);
12638
+ this.lastFacetValueIds = facetValueIds;
12639
+ }
12640
+ }
12641
+ addTagFn(item) {
12642
+ return { label: item };
12643
+ }
12644
+ isSearchHeaderSelected() {
12645
+ return this.selectComponent.itemsList.markedIndex === -1;
12646
+ }
12647
+ }
12648
+ ProductSearchInputComponent.decorators = [
12649
+ { type: Component, args: [{
12650
+ selector: 'vdr-product-search-input',
12651
+ template: "<ng-select\r\n [addTag]=\"addTagFn\"\r\n [placeholder]=\"'catalog.search-product-name-or-code' | translate\"\r\n [items]=\"facetValueResults\"\r\n [searchFn]=\"filterFacetResults\"\r\n [hideSelected]=\"true\"\r\n [multiple]=\"true\"\r\n [markFirst]=\"false\"\r\n (change)=\"onSelectChange($event)\"\r\n #selectComponent\r\n>\r\n <ng-template ng-header-tmp>\r\n <div\r\n class=\"search-header\"\r\n *ngIf=\"selectComponent.searchTerm\"\r\n [class.selected]=\"isSearchHeaderSelected()\"\r\n (click)=\"selectComponent.selectTag()\"\r\n >\r\n {{ 'catalog.search-for-term' | translate }}: {{ selectComponent.searchTerm }}\r\n </div>\r\n </ng-template>\r\n <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\r\n <ng-container *ngIf=\"item.facetValue\">\r\n <vdr-facet-value-chip\r\n [facetValue]=\"item.facetValue\"\r\n [removable]=\"true\"\r\n (remove)=\"clear(item)\"\r\n ></vdr-facet-value-chip>\r\n </ng-container>\r\n <ng-container *ngIf=\"!item.facetValue\">\r\n <vdr-chip [icon]=\"'times'\" (iconClick)=\"clear(item)\">\"{{ item.label }}\"</vdr-chip>\r\n </ng-container>\r\n </ng-template>\r\n <ng-template ng-option-tmp let-item=\"item\" let-index=\"index\" let-search=\"searchTerm\">\r\n <ng-container *ngIf=\"item.facetValue\">\r\n <vdr-facet-value-chip [facetValue]=\"item.facetValue\" [removable]=\"false\"></vdr-facet-value-chip>\r\n </ng-container>\r\n </ng-template>\r\n</ng-select>\r\n",
12652
+ changeDetection: ChangeDetectionStrategy.OnPush,
12653
+ providers: [{ provide: SELECTION_MODEL_FACTORY, useValue: ɵ0 }],
12654
+ styles: [":host{margin-top:6px;display:block;width:100%}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value{background:none;margin:0}:host ::ng-deep .ng-dropdown-panel-items div.ng-option:last-child{display:none}:host ::ng-deep .ng-dropdown-panel .ng-dropdown-header{border:none;padding:0}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container{padding:0}:host ::ng-deep .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder{padding-left:8px}ng-select{width:100%;min-width:300px;margin-right:12px}.search-header{padding:8px 10px;border-bottom:1px solid var(--color-component-border-100);cursor:pointer}.search-header.selected,.search-header:hover{background-color:var(--color-component-bg-200)}\n"]
12655
+ },] }
12656
+ ];
12657
+ ProductSearchInputComponent.propDecorators = {
12658
+ facetValueResults: [{ type: Input }],
12659
+ searchTermChange: [{ type: Output }],
12660
+ facetValueChange: [{ type: Output }],
12661
+ selectComponent: [{ type: ViewChild, args: ['selectComponent', { static: true },] }]
12662
+ };
12663
+
12206
12664
  /**
12207
12665
  * @description
12208
12666
  * A component for selecting product variants via an autocomplete-style select input.
@@ -13003,7 +13461,7 @@ RichTextEditorComponent.decorators = [
13003
13461
  },
13004
13462
  ProsemirrorService,
13005
13463
  ],
13006
- styles: ["@charset \"UTF-8\";::ng-deep .ProseMirror{position:relative}::ng-deep .ProseMirror{word-wrap:break-word;white-space:pre-wrap;-webkit-font-variant-ligatures:none;font-feature-settings:none;font-variant-ligatures:none}::ng-deep .ProseMirror pre{white-space:pre-wrap}::ng-deep .ProseMirror li{position:relative}::ng-deep .ProseMirror-hideselection *::selection{background:transparent}::ng-deep .ProseMirror-hideselection *::-moz-selection{background:transparent}::ng-deep .ProseMirror-hideselection{caret-color:transparent}::ng-deep .ProseMirror-selectednode{outline:2px solid var(--color-primary-500)}::ng-deep li.ProseMirror-selectednode{outline:none}::ng-deep li.ProseMirror-selectednode:after{content:\"\";position:absolute;left:-32px;right:-2px;top:-2px;bottom:-2px;border:2px solid var(--color-primary-500);pointer-events:none}::ng-deep .ProseMirror-textblock-dropdown{min-width:3em}::ng-deep .ProseMirror-menu{margin:0 -4px;line-height:1}::ng-deep .ProseMirror-tooltip .ProseMirror-menu{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;white-space:pre}::ng-deep .ProseMirror-menuitem{margin-right:3px;display:inline-block}::ng-deep .ProseMirror-menuseparator{border-right:1px solid var(--color-component-border-200);margin-right:3px}::ng-deep .ProseMirror-menu-dropdown,::ng-deep .ProseMirror-menu-dropdown-menu{font-size:90%;white-space:nowrap}::ng-deep .ProseMirror-menu-dropdown{vertical-align:1px;cursor:pointer;position:relative;padding-right:15px}::ng-deep .ProseMirror-menu-dropdown-wrap{padding:1px 0 1px 4px;display:inline-block;position:relative}::ng-deep .ProseMirror-menu-dropdown:after{content:\"\";border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid currentColor;opacity:.6;position:absolute;right:4px;top:calc(50% - 2px)}::ng-deep .ProseMirror-menu-dropdown-menu,::ng-deep .ProseMirror-menu-submenu{position:absolute;background:white;color:var(--color-grey-600);border:1px solid var(--color-component-border-200);padding:2px}::ng-deep .ProseMirror-menu-dropdown-menu{z-index:15;min-width:6em}::ng-deep .ProseMirror-menu-dropdown-item{cursor:pointer;padding:2px 8px 2px 4px}::ng-deep .ProseMirror-menu-dropdown-item:hover{background:var(--color-component-bg-100)}::ng-deep .ProseMirror-menu-submenu-wrap{position:relative;margin-right:-4px}::ng-deep .ProseMirror-menu-submenu-label:after{content:\"\";border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid currentColor;opacity:.6;position:absolute;right:4px;top:calc(50% - 4px)}::ng-deep .ProseMirror-menu-submenu{display:none;min-width:4em;left:100%;top:-3px}::ng-deep .ProseMirror-menu-active{background:var(--color-component-bg-100);border-radius:4px}::ng-deep .ProseMirror-menu-disabled{opacity:.3}::ng-deep .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu,::ng-deep .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu{display:block}::ng-deep .ProseMirror-menubar{border-top-left-radius:inherit;border-top-right-radius:inherit;position:relative;min-height:1em;color:var(--color-grey-600);padding:1px 6px;top:0;left:0;right:0;background:var(--color-component-bg-100);z-index:10;box-sizing:border-box;overflow:visible}::ng-deep .ProseMirror-icon{display:inline-block;line-height:.8;vertical-align:-2px;padding:2px 8px;cursor:pointer}::ng-deep .ProseMirror-menu-disabled.ProseMirror-icon{cursor:default}::ng-deep .ProseMirror-icon svg{fill:currentColor;height:1em}::ng-deep .ProseMirror-icon span{vertical-align:text-top}::ng-deep .ProseMirror-gapcursor{display:none;pointer-events:none;position:absolute}::ng-deep .ProseMirror-gapcursor:after{content:\"\";display:block;position:absolute;top:-2px;width:20px;border-top:1px solid black;animation:ProseMirror-cursor-blink 1.1s steps(2,start) infinite}@keyframes ProseMirror-cursor-blink{to{visibility:hidden}}::ng-deep .ProseMirror-focused .ProseMirror-gapcursor{display:block}::ng-deep .ProseMirror ul,::ng-deep .ProseMirror ol{padding-left:30px;list-style-position:initial}::ng-deep .ProseMirror blockquote{padding-left:1em;border-left:3px solid var(--color-grey-100);margin-left:0;margin-right:0}::ng-deep .ProseMirror-prompt{background:white;padding:5px 10px 5px 15px;border:1px solid silver;position:fixed;border-radius:3px;z-index:11;box-shadow:-.5px 2px 5px #0003}::ng-deep .ProseMirror-prompt h5{margin:0;font-weight:normal;font-size:100%;color:var(--color-grey-500)}::ng-deep .ProseMirror-prompt input[type=text],::ng-deep .ProseMirror-prompt textarea{background:var(--color-component-bg-100);border:none;outline:none}::ng-deep .ProseMirror-prompt input[type=text]{padding:0 4px}::ng-deep .ProseMirror-prompt-close{position:absolute;left:2px;top:1px;color:var(--color-grey-400);border:none;background:transparent;padding:0}::ng-deep .ProseMirror-prompt-close:after{content:\"\\e2\\153\\2022\";font-size:12px}::ng-deep .ProseMirror-invalid{background:var(--color-warning-200);border:1px solid var(--color-warning-300);border-radius:4px;padding:5px 10px;position:absolute;min-width:10em}::ng-deep .ProseMirror-prompt-buttons{margin-top:5px;display:none}::ng-deep #editor,::ng-deep .editor{background:var(--color-form-input-bg);color:#000;background-clip:padding-box;border-radius:4px;border:2px solid rgba(0,0,0,.2);padding:5px 0;margin-bottom:23px}::ng-deep .ProseMirror p:first-child,::ng-deep .ProseMirror h1:first-child,::ng-deep .ProseMirror h2:first-child,::ng-deep .ProseMirror h3:first-child,::ng-deep .ProseMirror h4:first-child,::ng-deep .ProseMirror h5:first-child,::ng-deep .ProseMirror h6:first-child{margin-top:10px}::ng-deep .ProseMirror{padding:4px 8px 4px 14px;line-height:1.2;outline:none}::ng-deep .ProseMirror p{margin-bottom:.5rem;color:var(--color-grey-800)!important}:host{display:block;max-width:710px;margin-bottom:.5rem}:host.readonly ::ng-deep .ProseMirror-menubar{display:none}::ng-deep .ProseMirror-menubar{position:sticky;top:24px;margin-top:6px;border:1px solid var(--color-component-border-200);border-bottom:none;background-color:var(--color-component-bg-200);color:var(--color-icon-button);padding:6px 12px;display:flex;flex-wrap:wrap}::ng-deep .vdr-prosemirror{background:var(--color-form-input-bg);min-height:128px;min-width:200px;border:1px solid var(--color-component-border-200);border-radius:0 0 3px 3px;transition:border-color .2s;overflow:auto;text-align:initial}::ng-deep .vdr-prosemirror:focus{border-color:var(--color-primary-500)!important;box-shadow:0 0 1px 1px var(--color-primary-100)}::ng-deep .vdr-prosemirror hr{padding:2px 10px;border:none;margin:1em 0}::ng-deep .vdr-prosemirror hr:after{content:\"\";display:block;height:1px;background-color:silver;line-height:2px}::ng-deep .vdr-prosemirror img{cursor:default;max-width:100%}\n"]
13464
+ styles: ["@charset \"UTF-8\";::ng-deep .ProseMirror{position:relative}::ng-deep .ProseMirror{word-wrap:break-word;white-space:pre-wrap;-webkit-font-variant-ligatures:none;font-feature-settings:none;font-variant-ligatures:none}::ng-deep .ProseMirror pre{white-space:pre-wrap}::ng-deep .ProseMirror li{position:relative}::ng-deep .ProseMirror-hideselection *::selection{background:transparent}::ng-deep .ProseMirror-hideselection *::-moz-selection{background:transparent}::ng-deep .ProseMirror-hideselection{caret-color:transparent}::ng-deep .ProseMirror-selectednode{outline:2px solid var(--color-primary-500)}::ng-deep li.ProseMirror-selectednode{outline:none}::ng-deep li.ProseMirror-selectednode:after{content:\"\";position:absolute;left:-32px;right:-2px;top:-2px;bottom:-2px;border:2px solid var(--color-primary-500);pointer-events:none}::ng-deep .ProseMirror-textblock-dropdown{min-width:3em}::ng-deep .ProseMirror-menu{margin:0 -4px;line-height:1}::ng-deep .ProseMirror-tooltip .ProseMirror-menu{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;white-space:pre}::ng-deep .ProseMirror-menuitem{margin-right:3px;display:inline-block}::ng-deep .ProseMirror-menuseparator{border-right:1px solid var(--color-component-border-200);margin-right:3px}::ng-deep .ProseMirror-menu-dropdown,::ng-deep .ProseMirror-menu-dropdown-menu{font-size:90%;white-space:nowrap}::ng-deep .ProseMirror-menu-dropdown{vertical-align:1px;cursor:pointer;position:relative;padding-right:15px}::ng-deep .ProseMirror-menu-dropdown-wrap{padding:1px 0 1px 4px;display:inline-block;position:relative}::ng-deep .ProseMirror-menu-dropdown:after{content:\"\";border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid currentColor;opacity:.6;position:absolute;right:4px;top:calc(50% - 2px)}::ng-deep .ProseMirror-menu-dropdown-menu,::ng-deep .ProseMirror-menu-submenu{position:absolute;background:white;color:var(--color-grey-600);border:1px solid var(--color-component-border-200);padding:2px}::ng-deep .ProseMirror-menu-dropdown-menu{z-index:15;min-width:6em}::ng-deep .ProseMirror-menu-dropdown-item{cursor:pointer;padding:2px 8px 2px 4px}::ng-deep .ProseMirror-menu-dropdown-item:hover{background:var(--color-component-bg-100)}::ng-deep .ProseMirror-menu-submenu-wrap{position:relative;margin-right:-4px}::ng-deep .ProseMirror-menu-submenu-label:after{content:\"\";border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid currentColor;opacity:.6;position:absolute;right:4px;top:calc(50% - 4px)}::ng-deep .ProseMirror-menu-submenu{display:none;min-width:4em;left:100%;top:-3px}::ng-deep .ProseMirror-menu-active{background:var(--color-component-bg-100);border-radius:4px}::ng-deep .ProseMirror-menu-disabled{opacity:.3}::ng-deep .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu,::ng-deep .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu{display:block}::ng-deep .ProseMirror-menubar{border-top-left-radius:inherit;border-top-right-radius:inherit;position:relative;min-height:1em;color:var(--color-grey-600);padding:1px 6px;top:0;left:0;right:0;background:var(--color-component-bg-100);z-index:10;box-sizing:border-box;overflow:visible}::ng-deep .ProseMirror-icon{display:inline-block;line-height:.8;vertical-align:-2px;padding:2px 8px;cursor:pointer}::ng-deep .ProseMirror-menu-disabled.ProseMirror-icon{cursor:default}::ng-deep .ProseMirror-icon svg{fill:currentColor;height:1em}::ng-deep .ProseMirror-icon span{vertical-align:text-top}::ng-deep .ProseMirror-gapcursor{display:none;pointer-events:none;position:absolute}::ng-deep .ProseMirror-gapcursor:after{content:\"\";display:block;position:absolute;top:-2px;width:20px;border-top:1px solid black;animation:ProseMirror-cursor-blink 1.1s steps(2,start) infinite}@keyframes ProseMirror-cursor-blink{to{visibility:hidden}}::ng-deep .ProseMirror-focused .ProseMirror-gapcursor{display:block}::ng-deep .ProseMirror ul,::ng-deep .ProseMirror ol{padding-left:30px;list-style-position:initial}::ng-deep .ProseMirror blockquote{padding-left:1em;border-left:3px solid var(--color-grey-100);margin-left:0;margin-right:0}::ng-deep .ProseMirror-prompt{background:white;padding:5px 10px 5px 15px;border:1px solid silver;position:fixed;border-radius:3px;z-index:11;box-shadow:-.5px 2px 5px #0003}::ng-deep .ProseMirror-prompt h5{margin:0;font-weight:normal;font-size:100%;color:var(--color-grey-500)}::ng-deep .ProseMirror-prompt input[type=text],::ng-deep .ProseMirror-prompt textarea{background:var(--color-component-bg-100);border:none;outline:none}::ng-deep .ProseMirror-prompt input[type=text]{padding:0 4px}::ng-deep .ProseMirror-prompt-close{position:absolute;left:2px;top:1px;color:var(--color-grey-400);border:none;background:transparent;padding:0}::ng-deep .ProseMirror-prompt-close:after{content:\"\\e2\\153\\2022\";font-size:12px}::ng-deep .ProseMirror-invalid{background:var(--color-warning-200);border:1px solid var(--color-warning-300);border-radius:4px;padding:5px 10px;position:absolute;min-width:10em}::ng-deep .ProseMirror-prompt-buttons{margin-top:5px;display:none}::ng-deep #editor,::ng-deep .editor{background:var(--color-form-input-bg);color:#000;background-clip:padding-box;border-radius:4px;border:2px solid rgba(0,0,0,.2);padding:5px 0;margin-bottom:23px}::ng-deep .ProseMirror p:first-child,::ng-deep .ProseMirror h1:first-child,::ng-deep .ProseMirror h2:first-child,::ng-deep .ProseMirror h3:first-child,::ng-deep .ProseMirror h4:first-child,::ng-deep .ProseMirror h5:first-child,::ng-deep .ProseMirror h6:first-child{margin-top:10px}::ng-deep .ProseMirror{padding:4px 8px 4px 14px;line-height:1.2;outline:none}::ng-deep .ProseMirror p{margin-bottom:.5rem;color:var(--color-grey-800)!important}:host{display:block;max-width:710px;margin-bottom:.5rem}:host.readonly ::ng-deep .ProseMirror-menubar{display:none}::ng-deep .ProseMirror-menubar{position:sticky;top:24px;margin-top:6px;border:1px solid var(--color-component-border-200);border-bottom:none;background-color:var(--color-component-bg-200);color:var(--color-icon-button);border-radius:var(--border-radius-input) var(--border-radius-input) 0 0;padding:6px 12px;display:flex;flex-wrap:wrap}::ng-deep .vdr-prosemirror{background:var(--color-form-input-bg);min-height:128px;min-width:200px;border:1px solid var(--color-component-border-200);border-radius:0 0 var(--border-radius-input) var(--border-radius-input);transition:border-color .2s;overflow:auto;text-align:initial}::ng-deep .vdr-prosemirror:focus{border-color:var(--color-primary-500)!important;box-shadow:0 0 1px 1px var(--color-primary-100)}::ng-deep .vdr-prosemirror hr{padding:2px 10px;border:none;margin:1em 0}::ng-deep .vdr-prosemirror hr:after{content:\"\";display:block;height:1px;background-color:silver;line-height:2px}::ng-deep .vdr-prosemirror img{cursor:default;max-width:100%}\n"]
13007
13465
  },] }
13008
13466
  ];
13009
13467
  RichTextEditorComponent.ctorParameters = () => [
@@ -13024,6 +13482,7 @@ class SelectToggleComponent {
13024
13482
  constructor() {
13025
13483
  this.size = 'large';
13026
13484
  this.selected = false;
13485
+ this.hiddenWhenOff = false;
13027
13486
  this.disabled = false;
13028
13487
  this.selectedChange = new EventEmitter();
13029
13488
  }
@@ -13031,14 +13490,15 @@ class SelectToggleComponent {
13031
13490
  SelectToggleComponent.decorators = [
13032
13491
  { type: Component, args: [{
13033
13492
  selector: 'vdr-select-toggle',
13034
- template: "<div\r\n class=\"toggle\"\r\n [class.disabled]=\"disabled\"\r\n [class.small]=\"size === 'small'\"\r\n [attr.tabindex]=\"disabled ? null : 0\"\r\n [class.selected]=\"selected\"\r\n (keydown.enter)=\"selectedChange.emit(!selected)\"\r\n (keydown.space)=\"$event.preventDefault(); selectedChange.emit(!selected)\"\r\n (click)=\"selectedChange.emit(!selected)\"\r\n>\r\n <clr-icon shape=\"check\" [attr.size]=\"size === 'small' ? 16 : 32\"></clr-icon>\r\n</div>\r\n<div class=\"toggle-label\" [class.disabled]=\"disabled\" *ngIf=\"label\" (click)=\"selectedChange.emit(!selected)\">\r\n {{ label }}\r\n</div>\r\n",
13493
+ template: "<div\r\n class=\"toggle\"\r\n [class.hide-when-off]=\"hiddenWhenOff\"\r\n [class.disabled]=\"disabled\"\r\n [class.small]=\"size === 'small'\"\r\n [attr.tabindex]=\"disabled ? null : 0\"\r\n [class.selected]=\"selected\"\r\n (keydown.enter)=\"selectedChange.emit(!selected)\"\r\n (keydown.space)=\"$event.preventDefault(); selectedChange.emit(!selected)\"\r\n (click)=\"selectedChange.emit(!selected)\"\r\n>\r\n <clr-icon shape=\"check-circle\" [attr.size]=\"size === 'small' ? 24 : 32\"></clr-icon>\r\n</div>\r\n<div class=\"toggle-label\" [class.disabled]=\"disabled\" *ngIf=\"label\" (click)=\"selectedChange.emit(!selected)\">\r\n {{ label }}\r\n</div>\r\n",
13035
13494
  changeDetection: ChangeDetectionStrategy.OnPush,
13036
- styles: [":host{display:flex;align-items:center;justify-content:center}.toggle{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;cursor:pointer;background-color:var(--color-component-bg-100);border:2px solid var(--color-component-border-300);padding:0 6px;border-radius:50%;width:32px;height:32px;display:flex;align-items:center;justify-content:center;color:var(--color-grey-300);transition:background-color .2s,border .2s}.toggle.small{width:24px;height:24px}.toggle:not(.disabled):hover{border-color:var(--color-success-500);background-color:var(--color-success-400);opacity:.9}.toggle.selected{background-color:var(--color-success-500);border-color:var(--color-success-600);color:#fff}.toggle.selected:not(.disabled):hover{background-color:var(--color-success-500);border-color:var(--color-success-400);opacity:.9}.toggle:focus{outline:none;box-shadow:0 0 2px 2px var(--color-primary-500)}.toggle.disabled{cursor:default}.toggle-label{flex:1;margin-left:6px;text-align:left;font-size:12px}.toggle-label:not(.disabled){cursor:pointer}\n"]
13495
+ styles: [":host{display:flex;align-items:center;justify-content:center}.toggle{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;cursor:pointer;color:var(--color-grey-300);background-color:var(--color-component-bg-100);border-radius:50%;top:-12px;left:-12px;transition:opacity .2s,color .2s}.toggle.hide-when-off{opacity:0}.toggle.small{width:24px;height:24px}.toggle:not(.disabled):hover{color:var(--color-success-400);opacity:.9}.toggle.selected{opacity:1;color:var(--color-success-500)}.toggle.selected:not(.disabled):hover{color:var(--color-success-400);opacity:.9}.toggle:focus{outline:none;box-shadow:0 0 2px 2px var(--color-primary-500)}.toggle.disabled{cursor:default}.toggle-label{flex:1;margin-left:6px;text-align:left;font-size:12px}.toggle-label:not(.disabled){cursor:pointer}\n"]
13037
13496
  },] }
13038
13497
  ];
13039
13498
  SelectToggleComponent.propDecorators = {
13040
13499
  size: [{ type: Input }],
13041
13500
  selected: [{ type: Input }],
13501
+ hiddenWhenOff: [{ type: Input }],
13042
13502
  disabled: [{ type: Input }],
13043
13503
  label: [{ type: Input }],
13044
13504
  selectedChange: [{ type: Output }]
@@ -13792,7 +14252,7 @@ class RelationAssetInputComponent {
13792
14252
  this.dataService = dataService;
13793
14253
  }
13794
14254
  ngOnInit() {
13795
- this.asset$ = this.parentFormControl.valueChanges.pipe(startWith(this.parentFormControl.value), map(asset => asset === null || asset === void 0 ? void 0 : asset.id), distinctUntilChanged(), switchMap(id => {
14255
+ this.asset$ = this.formControl.valueChanges.pipe(startWith(this.formControl.value), map(asset => asset === null || asset === void 0 ? void 0 : asset.id), distinctUntilChanged(), switchMap(id => {
13796
14256
  if (id) {
13797
14257
  return this.dataService.product.getAsset(id).mapStream(data => data.asset || undefined);
13798
14258
  }
@@ -13811,14 +14271,14 @@ class RelationAssetInputComponent {
13811
14271
  })
13812
14272
  .subscribe(result => {
13813
14273
  if (result && result.length) {
13814
- this.parentFormControl.setValue(result[0]);
13815
- this.parentFormControl.markAsDirty();
14274
+ this.formControl.setValue(result[0]);
14275
+ this.formControl.markAsDirty();
13816
14276
  }
13817
14277
  });
13818
14278
  }
13819
14279
  remove() {
13820
- this.parentFormControl.setValue(null);
13821
- this.parentFormControl.markAsDirty();
14280
+ this.formControl.setValue(null);
14281
+ this.formControl.markAsDirty();
13822
14282
  }
13823
14283
  previewAsset(asset) {
13824
14284
  this.modalService
@@ -13830,6 +14290,7 @@ class RelationAssetInputComponent {
13830
14290
  .subscribe();
13831
14291
  }
13832
14292
  }
14293
+ RelationAssetInputComponent.id = 'asset-form-input';
13833
14294
  RelationAssetInputComponent.decorators = [
13834
14295
  { type: Component, args: [{
13835
14296
  selector: 'vdr-relation-asset-input',
@@ -13844,7 +14305,7 @@ RelationAssetInputComponent.ctorParameters = () => [
13844
14305
  ];
13845
14306
  RelationAssetInputComponent.propDecorators = {
13846
14307
  readonly: [{ type: Input }],
13847
- parentFormControl: [{ type: Input }],
14308
+ formControl: [{ type: Input, args: ['parentFormControl',] }],
13848
14309
  config: [{ type: Input }]
13849
14310
  };
13850
14311
 
@@ -15034,6 +15495,8 @@ const DECLARATIONS = [
15034
15495
  UiExtensionPointComponent,
15035
15496
  CustomDetailComponentHostComponent,
15036
15497
  AssetPreviewLinksComponent,
15498
+ ProductMultiSelectorDialogComponent,
15499
+ ProductSearchInputComponent,
15037
15500
  ];
15038
15501
  const DYNAMIC_FORM_INPUTS = [
15039
15502
  TextFormInputComponent,
@@ -15057,6 +15520,8 @@ const DYNAMIC_FORM_INPUTS = [
15057
15520
  TextareaFormInputComponent,
15058
15521
  RichTextFormInputComponent,
15059
15522
  JsonEditorFormInputComponent,
15523
+ ProductMultiSelectorFormInputComponent,
15524
+ CombinationModeFormInputComponent,
15060
15525
  ];
15061
15526
  class SharedModule {
15062
15527
  }
@@ -15594,7 +16059,7 @@ function patchObject(obj, patch) {
15594
16059
  }
15595
16060
 
15596
16061
  // Auto-generated by the set-version.js script.
15597
- const ADMIN_UI_VERSION = '1.5.2';
16062
+ const ADMIN_UI_VERSION = '1.6.0';
15598
16063
 
15599
16064
  /**
15600
16065
  * Responsible for registering dashboard widget components and querying for layouts.
@@ -15814,5 +16279,5 @@ function unicodePatternValidator(patternRe) {
15814
16279
  * Generated bundle index. Do not edit.
15815
16280
  */
15816
16281
 
15817
- export { ADDRESS_FRAGMENT, ADD_CUSTOMERS_TO_GROUP, ADD_MANUAL_PAYMENT_TO_ORDER, ADD_MEMBERS_TO_ZONE, ADD_NOTE_TO_CUSTOMER, ADD_NOTE_TO_ORDER, ADD_OPTION_GROUP_TO_PRODUCT, ADD_OPTION_TO_GROUP, ADMINISTRATOR_FRAGMENT, ADMIN_UI_VERSION, ALL_CUSTOM_FIELDS_FRAGMENT, ASSET_FRAGMENT, ASSIGN_PRODUCTS_TO_CHANNEL, ASSIGN_ROLE_TO_ADMINISTRATOR, ASSIGN_VARIANTS_TO_CHANNEL, ATTEMPT_LOGIN, AUTH_REDIRECT_PARAM, ActionBarComponent, ActionBarItemsComponent, ActionBarLeftComponent, ActionBarRightComponent, AddressFormComponent, AdjustmentType, AdministratorDataService, AffixedInputComponent, AppComponent, AppComponentModule, AppShellComponent, AssetFileInputComponent, AssetGalleryComponent, AssetPickerDialogComponent, AssetPreviewComponent, AssetPreviewDialogComponent, AssetPreviewLinksComponent, AssetPreviewPipe, AssetSearchInputComponent, AssetType, AuthDataService, AuthGuard, AuthService, BOOLEAN_CUSTOM_FIELD_FRAGMENT, BaseDataService, BaseDetailComponent, BaseEntityResolver, BaseListComponent, BooleanFormInputComponent, BreadcrumbComponent, CANCEL_JOB, CANCEL_ORDER, CHANNEL_FRAGMENT, COLLECTION_FRAGMENT, CONFIGURABLE_OPERATION_DEF_FRAGMENT, CONFIGURABLE_OPERATION_FRAGMENT, COUNTRY_FRAGMENT, CREATE_ADMINISTRATOR, CREATE_ASSETS, CREATE_CHANNEL, CREATE_COLLECTION, CREATE_COUNTRY, CREATE_CUSTOMER, CREATE_CUSTOMER_ADDRESS, CREATE_CUSTOMER_GROUP, CREATE_FACET, CREATE_FACET_VALUES, CREATE_FULFILLMENT, CREATE_PAYMENT_METHOD, CREATE_PRODUCT, CREATE_PRODUCT_OPTION_GROUP, CREATE_PRODUCT_VARIANTS, CREATE_PROMOTION, CREATE_ROLE, CREATE_SHIPPING_METHOD, CREATE_TAG, CREATE_TAX_CATEGORY, CREATE_TAX_RATE, CREATE_ZONE, CURRENT_USER_FRAGMENT, CUSTOMER_FRAGMENT, CUSTOMER_GROUP_FRAGMENT, CUSTOM_FIELD_CONFIG_FRAGMENT, CanDeactivateDetailGuard, ChannelAssignmentControlComponent, ChannelBadgeComponent, ChannelLabelPipe, ChannelSwitcherComponent, CheckJobsLink, ChipComponent, ClientDataService, CollectionDataService, ComponentRegistryService, ConfigurableInputComponent, CoreModule, CurrencyCode, CurrencyFormInputComponent, CurrencyInputComponent, CustomDetailComponentHostComponent, CustomDetailComponentService, CustomFieldComponentService, CustomFieldControlComponent, CustomFieldLabelPipe, CustomHttpTranslationLoader, CustomerDataService, CustomerGroupFormInputComponent, CustomerLabelComponent, DATE_TIME_CUSTOM_FIELD_FRAGMENT, DELETE_ADMINISTRATOR, DELETE_ASSETS, DELETE_CHANNEL, DELETE_COLLECTION, DELETE_COUNTRY, DELETE_CUSTOMER, DELETE_CUSTOMER_ADDRESS, DELETE_CUSTOMER_GROUP, DELETE_CUSTOMER_NOTE, DELETE_FACET, DELETE_FACET_VALUES, DELETE_ORDER_NOTE, DELETE_PAYMENT_METHOD, DELETE_PRODUCT, DELETE_PRODUCT_VARIANT, DELETE_PROMOTION, DELETE_ROLE, DELETE_SHIPPING_METHOD, DELETE_TAG, DELETE_TAX_CATEGORY, DELETE_TAX_RATE, DELETE_ZONE, DISCOUNT_FRAGMENT, DashboardWidgetService, DataModule, DataService, DataTableColumnComponent, DataTableComponent, DateFormInputComponent, DatetimePickerComponent, DatetimePickerService, DefaultInterceptor, DeletionResult, DialogButtonsDirective, DialogComponentOutletComponent, DialogTitleDirective, DisabledDirective, DropdownComponent, DropdownItemDirective, DropdownMenuComponent, DropdownTriggerDirective, DurationPipe, DynamicFormInputComponent, ERROR_RESULT_FRAGMENT, EditNoteDialogComponent, EmptyPlaceholderComponent, EntityInfoComponent, ErrorCode, ExtensionHostComponent, ExtensionHostConfig, ExtensionHostService, ExternalImageDialogComponent, FACET_VALUE_FRAGMENT, FACET_WITH_VALUES_FRAGMENT, FLOAT_CUSTOM_FIELD_FRAGMENT, FULFILLMENT_FRAGMENT, FacetDataService, FacetValueChipComponent, FacetValueFormInputComponent, FacetValueSelectorComponent, FetchAdapter, FileSizePipe, FocalPointControlComponent, FormFieldComponent, FormFieldControlDirective, FormItemComponent, FormattedAddressComponent, GET_ACTIVE_ADMINISTRATOR, GET_ACTIVE_CHANNEL, GET_ADJUSTMENT_OPERATIONS, GET_ADMINISTRATOR, GET_ADMINISTRATORS, GET_ASSET, GET_ASSET_LIST, GET_AVAILABLE_COUNTRIES, GET_CHANNEL, GET_CHANNELS, GET_CLIENT_STATE, GET_COLLECTION, GET_COLLECTION_CONTENTS, GET_COLLECTION_FILTERS, GET_COLLECTION_LIST, GET_COUNTRY, GET_COUNTRY_LIST, GET_CURRENT_USER, GET_CUSTOMER, GET_CUSTOMER_GROUPS, GET_CUSTOMER_GROUP_WITH_CUSTOMERS, GET_CUSTOMER_HISTORY, GET_CUSTOMER_LIST, GET_FACET_LIST, GET_FACET_WITH_VALUES, GET_GLOBAL_SETTINGS, GET_JOBS_BY_ID, GET_JOBS_LIST, GET_JOB_INFO, GET_JOB_QUEUE_LIST, GET_NEWTORK_STATUS, GET_ORDER, GET_ORDERS_LIST, GET_ORDER_HISTORY, GET_ORDER_SUMMARY, GET_PAYMENT_METHOD, GET_PAYMENT_METHOD_LIST, GET_PAYMENT_METHOD_OPERATIONS, GET_PENDING_SEARCH_INDEX_UPDATES, GET_PRODUCT_LIST, GET_PRODUCT_OPTION_GROUP, GET_PRODUCT_OPTION_GROUPS, GET_PRODUCT_SIMPLE, GET_PRODUCT_VARIANT, GET_PRODUCT_VARIANT_LIST, GET_PRODUCT_VARIANT_LIST_SIMPLE, GET_PRODUCT_VARIANT_OPTIONS, GET_PRODUCT_WITH_VARIANTS, GET_PROMOTION, GET_PROMOTION_LIST, GET_ROLE, GET_ROLES, GET_SERVER_CONFIG, GET_SHIPPING_METHOD, GET_SHIPPING_METHOD_LIST, GET_SHIPPING_METHOD_OPERATIONS, GET_TAG, GET_TAG_LIST, GET_TAX_CATEGORIES, GET_TAX_CATEGORY, GET_TAX_RATE, GET_TAX_RATE_LIST, GET_TAX_RATE_LIST_SIMPLE, GET_UI_STATE, GET_USER_STATUS, GET_ZONE, GET_ZONES, GLOBAL_SETTINGS_FRAGMENT, GlobalFlag, HasPermissionPipe, HealthCheckService, HelpTooltipComponent, HistoryEntryDetailComponent, HistoryEntryType, HttpLoaderFactory, I18nService, INT_CUSTOM_FIELD_FRAGMENT, IfDefaultChannelActiveDirective, IfDirectiveBase, IfMultichannelDirective, IfPermissionsDirective, InjectableTranslateMessageFormatCompiler, ItemsPerPageControlsComponent, JOB_INFO_FRAGMENT, JobQueueService, JobState, JsonEditorFormInputComponent, LOCALE_STRING_CUSTOM_FIELD_FRAGMENT, LOG_OUT, LabeledDataComponent, LanguageCode, LanguageSelectorComponent, LinkDialogComponent, LocalStorageService, LocaleBasePipe, LocaleCurrencyNamePipe, LocaleCurrencyPipe, LocaleDatePipe, LocaleLanguageNamePipe, LocaleRegionNamePipe, LogicalOperator, MODIFY_ORDER, MOVE_COLLECTION, MainNavComponent, ManageTagsDialogComponent, ModalDialogComponent, ModalService, NavBuilderService, NotificationComponent, NotificationService, NumberFormInputComponent, ORDER_ADDRESS_FRAGMENT, ORDER_DETAIL_FRAGMENT, ORDER_FRAGMENT, ORDER_LINE_FRAGMENT, ObjectTreeComponent, OmitTypenameLink, OrderDataService, OrderStateLabelComponent, OverlayHostComponent, OverlayHostService, PAYMENT_METHOD_FRAGMENT, PRODUCT_DETAIL_FRAGMENT, PRODUCT_OPTION_FRAGMENT, PRODUCT_OPTION_GROUP_FRAGMENT, PRODUCT_OPTION_GROUP_WITH_OPTIONS_FRAGMENT, PRODUCT_SELECTOR_SEARCH, PRODUCT_VARIANT_FRAGMENT, PROMOTION_FRAGMENT, PaginationControlsComponent, PasswordFormInputComponent, PercentageSuffixInputComponent, Permission, ProductDataService, ProductSelectorComponent, ProductSelectorFormInputComponent, PromotionDataService, ProsemirrorService, QueryResult, REFUND_FRAGMENT, REFUND_ORDER, REINDEX, RELATION_CUSTOM_FIELD_FRAGMENT, REMOVE_CUSTOMERS_FROM_GROUP, REMOVE_MEMBERS_FROM_ZONE, REMOVE_OPTION_GROUP_FROM_PRODUCT, REMOVE_PRODUCTS_FROM_CHANNEL, REMOVE_VARIANTS_FROM_CHANNEL, REQUEST_COMPLETED, REQUEST_STARTED, ROLE_FRAGMENT, RUN_PENDING_SEARCH_INDEX_UPDATES, RelationAssetInputComponent, RelationCardComponent, RelationCardDetailDirective, RelationCardPreviewDirective, RelationCustomerInputComponent, RelationFormInputComponent, RelationGenericInputComponent, RelationProductInputComponent, RelationProductVariantInputComponent, RelationSelectorDialogComponent, RichTextEditorComponent, RichTextFormInputComponent, SEARCH_PRODUCTS, SETTLE_PAYMENT, SETTLE_REFUND, SET_ACTIVE_CHANNEL, SET_AS_LOGGED_IN, SET_AS_LOGGED_OUT, SET_CONTENT_LANGUAGE, SET_DISPLAY_UI_EXTENSION_POINTS, SET_UI_LANGUAGE_AND_LOCALE, SET_UI_LOCALE, SET_UI_THEME, SHIPPING_METHOD_FRAGMENT, STRING_CUSTOM_FIELD_FRAGMENT, SelectFormInputComponent, SelectToggleComponent, SentenceCasePipe, ServerConfigService, SettingsDataService, SharedModule, ShippingMethodDataService, SimpleDialogComponent, SingleSearchSelectionModel, SingleSearchSelectionModelFactory, SortOrder, SortPipe, StateI18nTokenPipe, StatusBadgeComponent, StockMovementType, StringToColorPipe, TAG_FRAGMENT, TAX_CATEGORY_FRAGMENT, TAX_RATE_FRAGMENT, TEST_ELIGIBLE_SHIPPING_METHODS, TEST_SHIPPING_METHOD, TEXT_CUSTOM_FIELD_FRAGMENT, TRANSITION_FULFILLMENT_TO_STATE, TRANSITION_ORDER_TO_STATE, TRANSITION_PAYMENT_TO_STATE, TabbedCustomFieldsComponent, TableRowActionComponent, TagSelectorComponent, TextFormInputComponent, TextareaFormInputComponent, ThemeSwitcherComponent, TimeAgoPipe, TimelineEntryComponent, TitleInputComponent, UPDATE_ACTIVE_ADMINISTRATOR, UPDATE_ADMINISTRATOR, UPDATE_ASSET, UPDATE_CHANNEL, UPDATE_COLLECTION, UPDATE_COUNTRY, UPDATE_CUSTOMER, UPDATE_CUSTOMER_ADDRESS, UPDATE_CUSTOMER_GROUP, UPDATE_CUSTOMER_NOTE, UPDATE_FACET, UPDATE_FACET_VALUES, UPDATE_GLOBAL_SETTINGS, UPDATE_ORDER_CUSTOM_FIELDS, UPDATE_ORDER_NOTE, UPDATE_PAYMENT_METHOD, UPDATE_PRODUCT, UPDATE_PRODUCT_OPTION, UPDATE_PRODUCT_OPTION_GROUP, UPDATE_PRODUCT_VARIANTS, UPDATE_PROMOTION, UPDATE_ROLE, UPDATE_SHIPPING_METHOD, UPDATE_TAG, UPDATE_TAX_CATEGORY, UPDATE_TAX_RATE, UPDATE_USER_CHANNELS, UPDATE_ZONE, USER_STATUS_FRAGMENT, UiExtensionPointComponent, UiLanguageSwitcherDialogComponent, UserMenuComponent, ZONE_FRAGMENT, addActionBarItem, addCustomFields, addNavMenuItem, addNavMenuSection, blockQuoteRule, buildInputRules, buildKeymap, buildMenuItems, bulletListRule, canInsert, clientResolvers, codeBlockRule, configurableDefinitionToInstance, configurableOperationValueIsValid, createApollo, createResolveData, createUpdatedTranslatable, dayOfWeekIndex, defaultFormInputs, detailBreadcrumb, encodeConfigArgValue, findTranslation, flattenFacetValues, getAppConfig, getClientDefaults, getConfigArgValue, getDefaultConfigArgValue, getDefaultUiLanguage, getDefaultUiLocale, getLocales, getMarkRange, getServerLocation, headingRule, hostExternalFrame, initializeServerConfigService, insertImageItem, interpolateDescription, result as introspectionResult, isEntityCreateOrUpdateMutation, jsonValidator, linkItem, linkSelectPlugin, loadAppConfig, markActive, orderedListRule, registerCustomDetailComponent, registerCustomFieldComponent, registerDashboardWidget, registerDefaultFormInputs, registerFormInputComponent, removeReadonlyCustomFields, setDashboardWidgetLayout, stringToColor, toConfigurableOperationInput, transformRelationCustomFieldInputs, unicodePatternValidator, weekDayNames, ɵ1, ɵ10, ɵ2, ɵ3, ɵ4, ɵ5, ɵ6, ɵ7, ɵ8, ɵ9 };
16282
+ export { ADDRESS_FRAGMENT, ADD_CUSTOMERS_TO_GROUP, ADD_MANUAL_PAYMENT_TO_ORDER, ADD_MEMBERS_TO_ZONE, ADD_NOTE_TO_CUSTOMER, ADD_NOTE_TO_ORDER, ADD_OPTION_GROUP_TO_PRODUCT, ADD_OPTION_TO_GROUP, ADMINISTRATOR_FRAGMENT, ADMIN_UI_VERSION, ALL_CUSTOM_FIELDS_FRAGMENT, ASSET_FRAGMENT, ASSIGN_PRODUCTS_TO_CHANNEL, ASSIGN_ROLE_TO_ADMINISTRATOR, ASSIGN_VARIANTS_TO_CHANNEL, ATTEMPT_LOGIN, AUTH_REDIRECT_PARAM, ActionBarComponent, ActionBarItemsComponent, ActionBarLeftComponent, ActionBarRightComponent, AddressFormComponent, AdjustmentType, AdministratorDataService, AffixedInputComponent, AppComponent, AppComponentModule, AppShellComponent, AssetFileInputComponent, AssetGalleryComponent, AssetPickerDialogComponent, AssetPreviewComponent, AssetPreviewDialogComponent, AssetPreviewLinksComponent, AssetPreviewPipe, AssetSearchInputComponent, AssetType, AuthDataService, AuthGuard, AuthService, BOOLEAN_CUSTOM_FIELD_FRAGMENT, BaseDataService, BaseDetailComponent, BaseEntityResolver, BaseListComponent, BooleanFormInputComponent, BreadcrumbComponent, CANCEL_JOB, CANCEL_ORDER, CHANNEL_FRAGMENT, COLLECTION_FRAGMENT, CONFIGURABLE_OPERATION_DEF_FRAGMENT, CONFIGURABLE_OPERATION_FRAGMENT, COUNTRY_FRAGMENT, CREATE_ADMINISTRATOR, CREATE_ASSETS, CREATE_CHANNEL, CREATE_COLLECTION, CREATE_COUNTRY, CREATE_CUSTOMER, CREATE_CUSTOMER_ADDRESS, CREATE_CUSTOMER_GROUP, CREATE_FACET, CREATE_FACET_VALUES, CREATE_FULFILLMENT, CREATE_PAYMENT_METHOD, CREATE_PRODUCT, CREATE_PRODUCT_OPTION_GROUP, CREATE_PRODUCT_VARIANTS, CREATE_PROMOTION, CREATE_ROLE, CREATE_SHIPPING_METHOD, CREATE_TAG, CREATE_TAX_CATEGORY, CREATE_TAX_RATE, CREATE_ZONE, CURRENT_USER_FRAGMENT, CUSTOMER_FRAGMENT, CUSTOMER_GROUP_FRAGMENT, CUSTOM_FIELD_CONFIG_FRAGMENT, CanDeactivateDetailGuard, ChannelAssignmentControlComponent, ChannelBadgeComponent, ChannelLabelPipe, ChannelSwitcherComponent, CheckJobsLink, ChipComponent, ClientDataService, CollectionDataService, CombinationModeFormInputComponent, ComponentRegistryService, ConfigurableInputComponent, CoreModule, CurrencyCode, CurrencyFormInputComponent, CurrencyInputComponent, CustomDetailComponentHostComponent, CustomDetailComponentService, CustomFieldComponentService, CustomFieldControlComponent, CustomFieldLabelPipe, CustomHttpTranslationLoader, CustomerDataService, CustomerGroupFormInputComponent, CustomerLabelComponent, DATE_TIME_CUSTOM_FIELD_FRAGMENT, DELETE_ADMINISTRATOR, DELETE_ASSETS, DELETE_CHANNEL, DELETE_COLLECTION, DELETE_COUNTRY, DELETE_CUSTOMER, DELETE_CUSTOMER_ADDRESS, DELETE_CUSTOMER_GROUP, DELETE_CUSTOMER_NOTE, DELETE_FACET, DELETE_FACET_VALUES, DELETE_ORDER_NOTE, DELETE_PAYMENT_METHOD, DELETE_PRODUCT, DELETE_PRODUCT_VARIANT, DELETE_PROMOTION, DELETE_ROLE, DELETE_SHIPPING_METHOD, DELETE_TAG, DELETE_TAX_CATEGORY, DELETE_TAX_RATE, DELETE_ZONE, DISCOUNT_FRAGMENT, DashboardWidgetService, DataModule, DataService, DataTableColumnComponent, DataTableComponent, DateFormInputComponent, DatetimePickerComponent, DatetimePickerService, DefaultInterceptor, DeletionResult, DialogButtonsDirective, DialogComponentOutletComponent, DialogTitleDirective, DisabledDirective, DropdownComponent, DropdownItemDirective, DropdownMenuComponent, DropdownTriggerDirective, DurationPipe, DynamicFormInputComponent, ERROR_RESULT_FRAGMENT, EditNoteDialogComponent, EmptyPlaceholderComponent, EntityInfoComponent, ErrorCode, ExtensionHostComponent, ExtensionHostConfig, ExtensionHostService, ExternalImageDialogComponent, FACET_VALUE_FRAGMENT, FACET_WITH_VALUES_FRAGMENT, FLOAT_CUSTOM_FIELD_FRAGMENT, FULFILLMENT_FRAGMENT, FacetDataService, FacetValueChipComponent, FacetValueFormInputComponent, FacetValueSelectorComponent, FetchAdapter, FileSizePipe, FocalPointControlComponent, FormFieldComponent, FormFieldControlDirective, FormItemComponent, FormattedAddressComponent, GET_ACTIVE_ADMINISTRATOR, GET_ACTIVE_CHANNEL, GET_ADJUSTMENT_OPERATIONS, GET_ADMINISTRATOR, GET_ADMINISTRATORS, GET_ASSET, GET_ASSET_LIST, GET_AVAILABLE_COUNTRIES, GET_CHANNEL, GET_CHANNELS, GET_CLIENT_STATE, GET_COLLECTION, GET_COLLECTION_CONTENTS, GET_COLLECTION_FILTERS, GET_COLLECTION_LIST, GET_COUNTRY, GET_COUNTRY_LIST, GET_CURRENT_USER, GET_CUSTOMER, GET_CUSTOMER_GROUPS, GET_CUSTOMER_GROUP_WITH_CUSTOMERS, GET_CUSTOMER_HISTORY, GET_CUSTOMER_LIST, GET_FACET_LIST, GET_FACET_WITH_VALUES, GET_GLOBAL_SETTINGS, GET_JOBS_BY_ID, GET_JOBS_LIST, GET_JOB_INFO, GET_JOB_QUEUE_LIST, GET_NEWTORK_STATUS, GET_ORDER, GET_ORDERS_LIST, GET_ORDER_HISTORY, GET_ORDER_SUMMARY, GET_PAYMENT_METHOD, GET_PAYMENT_METHOD_LIST, GET_PAYMENT_METHOD_OPERATIONS, GET_PENDING_SEARCH_INDEX_UPDATES, GET_PRODUCT_LIST, GET_PRODUCT_OPTION_GROUP, GET_PRODUCT_OPTION_GROUPS, GET_PRODUCT_SIMPLE, GET_PRODUCT_VARIANT, GET_PRODUCT_VARIANT_LIST, GET_PRODUCT_VARIANT_LIST_SIMPLE, GET_PRODUCT_VARIANT_OPTIONS, GET_PRODUCT_WITH_VARIANTS, GET_PROMOTION, GET_PROMOTION_LIST, GET_ROLE, GET_ROLES, GET_SERVER_CONFIG, GET_SHIPPING_METHOD, GET_SHIPPING_METHOD_LIST, GET_SHIPPING_METHOD_OPERATIONS, GET_TAG, GET_TAG_LIST, GET_TAX_CATEGORIES, GET_TAX_CATEGORY, GET_TAX_RATE, GET_TAX_RATE_LIST, GET_TAX_RATE_LIST_SIMPLE, GET_UI_STATE, GET_USER_STATUS, GET_ZONE, GET_ZONES, GLOBAL_SETTINGS_FRAGMENT, GlobalFlag, HasPermissionPipe, HealthCheckService, HelpTooltipComponent, HistoryEntryDetailComponent, HistoryEntryType, HttpLoaderFactory, I18nService, INT_CUSTOM_FIELD_FRAGMENT, IfDefaultChannelActiveDirective, IfDirectiveBase, IfMultichannelDirective, IfPermissionsDirective, InjectableTranslateMessageFormatCompiler, ItemsPerPageControlsComponent, JOB_INFO_FRAGMENT, JobQueueService, JobState, JsonEditorFormInputComponent, LOCALE_STRING_CUSTOM_FIELD_FRAGMENT, LOG_OUT, LabeledDataComponent, LanguageCode, LanguageSelectorComponent, LinkDialogComponent, LocalStorageService, LocaleBasePipe, LocaleCurrencyNamePipe, LocaleCurrencyPipe, LocaleDatePipe, LocaleLanguageNamePipe, LocaleRegionNamePipe, LogicalOperator, MODIFY_ORDER, MOVE_COLLECTION, MainNavComponent, ManageTagsDialogComponent, ModalDialogComponent, ModalService, NavBuilderService, NotificationComponent, NotificationService, NumberFormInputComponent, ORDER_ADDRESS_FRAGMENT, ORDER_DETAIL_FRAGMENT, ORDER_FRAGMENT, ORDER_LINE_FRAGMENT, ObjectTreeComponent, OmitTypenameLink, OrderDataService, OrderStateLabelComponent, OverlayHostComponent, OverlayHostService, PAYMENT_METHOD_FRAGMENT, PREVIEW_COLLECTION_CONTENTS, PRODUCT_DETAIL_FRAGMENT, PRODUCT_OPTION_FRAGMENT, PRODUCT_OPTION_GROUP_FRAGMENT, PRODUCT_OPTION_GROUP_WITH_OPTIONS_FRAGMENT, PRODUCT_SELECTOR_SEARCH, PRODUCT_VARIANT_FRAGMENT, PROMOTION_FRAGMENT, PaginationControlsComponent, PasswordFormInputComponent, PercentageSuffixInputComponent, Permission, ProductDataService, ProductMultiSelectorDialogComponent, ProductMultiSelectorFormInputComponent, ProductSearchInputComponent, ProductSelectorComponent, ProductSelectorFormInputComponent, PromotionDataService, ProsemirrorService, QueryResult, REFUND_FRAGMENT, REFUND_ORDER, REINDEX, RELATION_CUSTOM_FIELD_FRAGMENT, REMOVE_CUSTOMERS_FROM_GROUP, REMOVE_MEMBERS_FROM_ZONE, REMOVE_OPTION_GROUP_FROM_PRODUCT, REMOVE_PRODUCTS_FROM_CHANNEL, REMOVE_VARIANTS_FROM_CHANNEL, REQUEST_COMPLETED, REQUEST_STARTED, ROLE_FRAGMENT, RUN_PENDING_SEARCH_INDEX_UPDATES, RelationAssetInputComponent, RelationCardComponent, RelationCardDetailDirective, RelationCardPreviewDirective, RelationCustomerInputComponent, RelationFormInputComponent, RelationGenericInputComponent, RelationProductInputComponent, RelationProductVariantInputComponent, RelationSelectorDialogComponent, RichTextEditorComponent, RichTextFormInputComponent, SEARCH_PRODUCTS, SETTLE_PAYMENT, SETTLE_REFUND, SET_ACTIVE_CHANNEL, SET_AS_LOGGED_IN, SET_AS_LOGGED_OUT, SET_CONTENT_LANGUAGE, SET_DISPLAY_UI_EXTENSION_POINTS, SET_UI_LANGUAGE_AND_LOCALE, SET_UI_LOCALE, SET_UI_THEME, SHIPPING_METHOD_FRAGMENT, STRING_CUSTOM_FIELD_FRAGMENT, SelectFormInputComponent, SelectToggleComponent, SelectionManager, SentenceCasePipe, ServerConfigService, SettingsDataService, SharedModule, ShippingMethodDataService, SimpleDialogComponent, SingleSearchSelectionModel, SingleSearchSelectionModelFactory, SortOrder, SortPipe, StateI18nTokenPipe, StatusBadgeComponent, StockMovementType, StringToColorPipe, TAG_FRAGMENT, TAX_CATEGORY_FRAGMENT, TAX_RATE_FRAGMENT, TEST_ELIGIBLE_SHIPPING_METHODS, TEST_SHIPPING_METHOD, TEXT_CUSTOM_FIELD_FRAGMENT, TRANSITION_FULFILLMENT_TO_STATE, TRANSITION_ORDER_TO_STATE, TRANSITION_PAYMENT_TO_STATE, TabbedCustomFieldsComponent, TableRowActionComponent, TagSelectorComponent, TextFormInputComponent, TextareaFormInputComponent, ThemeSwitcherComponent, TimeAgoPipe, TimelineEntryComponent, TitleInputComponent, UPDATE_ACTIVE_ADMINISTRATOR, UPDATE_ADMINISTRATOR, UPDATE_ASSET, UPDATE_CHANNEL, UPDATE_COLLECTION, UPDATE_COUNTRY, UPDATE_CUSTOMER, UPDATE_CUSTOMER_ADDRESS, UPDATE_CUSTOMER_GROUP, UPDATE_CUSTOMER_NOTE, UPDATE_FACET, UPDATE_FACET_VALUES, UPDATE_GLOBAL_SETTINGS, UPDATE_ORDER_CUSTOM_FIELDS, UPDATE_ORDER_NOTE, UPDATE_PAYMENT_METHOD, UPDATE_PRODUCT, UPDATE_PRODUCT_OPTION, UPDATE_PRODUCT_OPTION_GROUP, UPDATE_PRODUCT_VARIANTS, UPDATE_PROMOTION, UPDATE_ROLE, UPDATE_SHIPPING_METHOD, UPDATE_TAG, UPDATE_TAX_CATEGORY, UPDATE_TAX_RATE, UPDATE_USER_CHANNELS, UPDATE_ZONE, USER_STATUS_FRAGMENT, UiExtensionPointComponent, UiLanguageSwitcherDialogComponent, UserMenuComponent, ZONE_FRAGMENT, addActionBarItem, addCustomFields, addNavMenuItem, addNavMenuSection, blockQuoteRule, buildInputRules, buildKeymap, buildMenuItems, bulletListRule, canInsert, clientResolvers, codeBlockRule, configurableDefinitionToInstance, configurableOperationValueIsValid, createApollo, createResolveData, createUpdatedTranslatable, dayOfWeekIndex, defaultFormInputs, detailBreadcrumb, encodeConfigArgValue, findTranslation, flattenFacetValues, getAppConfig, getClientDefaults, getConfigArgValue, getDefaultConfigArgValue, getDefaultUiLanguage, getDefaultUiLocale, getLocales, getMarkRange, getServerLocation, headingRule, hostExternalFrame, initializeServerConfigService, insertImageItem, interpolateDescription, result as introspectionResult, isEntityCreateOrUpdateMutation, jsonValidator, linkItem, linkSelectPlugin, loadAppConfig, markActive, orderedListRule, registerCustomDetailComponent, registerCustomFieldComponent, registerDashboardWidget, registerDefaultFormInputs, registerFormInputComponent, removeReadonlyCustomFields, setDashboardWidgetLayout, stringToColor, toConfigurableOperationInput, transformRelationCustomFieldInputs, unicodePatternValidator, weekDayNames, ɵ1, ɵ10, ɵ2, ɵ3, ɵ4, ɵ5, ɵ6, ɵ7, ɵ8, ɵ9 };
15818
16283
  //# sourceMappingURL=vendure-admin-ui-core.js.map