@vendure/admin-ui 1.5.0 → 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 (123) hide show
  1. package/bundles/vendure-admin-ui-catalog.umd.js +236 -195
  2. package/bundles/vendure-admin-ui-catalog.umd.js.map +1 -1
  3. package/bundles/vendure-admin-ui-core.umd.js +2358 -1843
  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 +3 -3
  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/components/product-variants-list/product-variants-list.component.d.ts +1 -0
  19. package/catalog/providers/product-detail/product-detail.service.d.ts +2 -2
  20. package/catalog/public_api.d.ts +0 -1
  21. package/catalog/vendure-admin-ui-catalog.metadata.json +1 -1
  22. package/core/common/generated-types.d.ts +57 -3
  23. package/core/common/utilities/selection-manager.d.ts +23 -0
  24. package/core/common/version.d.ts +1 -1
  25. package/core/components/app-shell/app-shell.component.d.ts +1 -0
  26. package/core/data/definitions/collection-definitions.d.ts +1 -0
  27. package/core/data/providers/collection-data.service.d.ts +6 -2
  28. package/core/providers/local-storage/local-storage.service.d.ts +1 -0
  29. package/core/public_api.d.ts +5 -0
  30. package/core/shared/components/asset-gallery/asset-gallery.component.d.ts +21 -6
  31. package/core/shared/components/configurable-input/configurable-input.component.d.ts +7 -2
  32. package/core/shared/components/product-multi-selector-dialog/product-multi-selector-dialog.component.d.ts +35 -0
  33. package/{catalog → core/shared}/components/product-search-input/product-search-input.component.d.ts +1 -1
  34. package/core/shared/components/select-toggle/select-toggle.component.d.ts +1 -0
  35. package/core/shared/dynamic-form-inputs/combination-mode-form-input/combination-mode-form-input.component.d.ts +25 -0
  36. package/core/shared/dynamic-form-inputs/product-multi-selector-form-input/product-multi-selector-form-input.component.d.ts +20 -0
  37. package/core/shared/dynamic-form-inputs/register-dynamic-input-components.d.ts +3 -1
  38. package/core/shared/dynamic-form-inputs/relation-form-input/asset/relation-asset-input.component.d.ts +5 -2
  39. package/core/vendure-admin-ui-core.metadata.json +1 -1
  40. package/dashboard/vendure-admin-ui-dashboard.metadata.json +1 -1
  41. package/esm2015/catalog/catalog.module.js +1 -3
  42. package/esm2015/catalog/components/assets/assets.component.js +1 -1
  43. package/esm2015/catalog/components/collection-contents/collection-contents.component.js +51 -14
  44. package/esm2015/catalog/components/collection-detail/collection-detail.component.js +67 -29
  45. package/esm2015/catalog/components/collection-list/collection-list.component.js +30 -4
  46. package/esm2015/catalog/components/collection-tree/array-to-tree.js +3 -3
  47. package/esm2015/catalog/components/collection-tree/collection-tree-node.component.js +27 -4
  48. package/esm2015/catalog/components/collection-tree/collection-tree.component.js +4 -2
  49. package/esm2015/catalog/components/facet-detail/facet-detail.component.js +3 -3
  50. package/esm2015/catalog/components/product-detail/product-detail.component.js +1 -1
  51. package/esm2015/catalog/components/product-list/product-list.component.js +3 -3
  52. package/esm2015/catalog/components/product-variants-list/product-variants-list.component.js +10 -3
  53. package/esm2015/catalog/public_api.js +1 -2
  54. package/esm2015/core/app.component.module.js +1 -1
  55. package/esm2015/core/common/base-detail.component.js +1 -1
  56. package/esm2015/core/common/deactivate-aware.js +1 -1
  57. package/esm2015/core/common/generated-types.js +26 -1
  58. package/esm2015/core/common/introspection-result.js +255 -189
  59. package/esm2015/core/common/utilities/configurable-operation-utils.js +21 -3
  60. package/esm2015/core/common/utilities/selection-manager.js +64 -0
  61. package/esm2015/core/common/version.js +2 -2
  62. package/esm2015/core/components/app-shell/app-shell.component.js +4 -3
  63. package/esm2015/core/core.module.js +1 -1
  64. package/esm2015/core/data/definitions/collection-definitions.js +18 -1
  65. package/esm2015/core/data/definitions/order-definitions.js +2 -1
  66. package/esm2015/core/data/definitions/shared-definitions.js +29 -28
  67. package/esm2015/core/data/providers/collection-data.service.js +5 -2
  68. package/esm2015/core/providers/local-storage/local-storage.service.js +1 -1
  69. package/esm2015/core/public_api.js +6 -1
  70. package/esm2015/core/shared/components/asset-gallery/asset-gallery.component.js +24 -42
  71. package/esm2015/core/shared/components/asset-preview/asset-preview.component.js +4 -4
  72. package/esm2015/core/shared/components/configurable-input/configurable-input.component.js +13 -3
  73. package/esm2015/core/shared/components/help-tooltip/help-tooltip.component.js +1 -1
  74. package/esm2015/core/shared/components/product-multi-selector-dialog/product-multi-selector-dialog.component.js +129 -0
  75. package/esm2015/core/shared/components/product-search-input/product-search-input.component.js +104 -0
  76. package/esm2015/core/shared/components/rich-text-editor/rich-text-editor.component.js +1 -1
  77. package/esm2015/core/shared/components/select-toggle/select-toggle.component.js +5 -3
  78. package/esm2015/core/shared/dynamic-form-inputs/combination-mode-form-input/combination-mode-form-input.component.js +45 -0
  79. package/esm2015/core/shared/dynamic-form-inputs/product-multi-selector-form-input/product-multi-selector-form-input.component.js +53 -0
  80. package/esm2015/core/shared/dynamic-form-inputs/register-dynamic-input-components.js +5 -1
  81. package/esm2015/core/shared/dynamic-form-inputs/relation-form-input/asset/relation-asset-input.component.js +8 -7
  82. package/esm2015/core/shared/dynamic-form-inputs/select-form-input/select-form-input.component.js +1 -1
  83. package/esm2015/core/shared/shared.module.js +9 -1
  84. package/esm2015/dashboard/components/dashboard/dashboard.component.js +1 -1
  85. package/esm2015/dashboard/widgets/order-summary-widget/order-summary-widget.component.js +1 -1
  86. package/esm2015/login/components/login/login.component.js +3 -3
  87. package/esm2015/marketing/components/promotion-detail/promotion-detail.component.js +2 -2
  88. package/esm2015/order/components/order-editor/order-editor.component.js +1 -1
  89. package/esm2015/order/components/order-list/order-list.component.js +2 -2
  90. package/esm2015/order/components/order-table/order-table.component.js +1 -1
  91. package/fesm2015/vendure-admin-ui-catalog.js +191 -160
  92. package/fesm2015/vendure-admin-ui-catalog.js.map +1 -1
  93. package/fesm2015/vendure-admin-ui-core.js +2203 -1696
  94. package/fesm2015/vendure-admin-ui-core.js.map +1 -1
  95. package/fesm2015/vendure-admin-ui-dashboard.js +2 -2
  96. package/fesm2015/vendure-admin-ui-login.js +2 -2
  97. package/fesm2015/vendure-admin-ui-login.js.map +1 -1
  98. package/fesm2015/vendure-admin-ui-marketing.js +1 -1
  99. package/fesm2015/vendure-admin-ui-marketing.js.map +1 -1
  100. package/fesm2015/vendure-admin-ui-order.js +3 -3
  101. package/fesm2015/vendure-admin-ui-order.js.map +1 -1
  102. package/login/vendure-admin-ui-login.metadata.json +1 -1
  103. package/marketing/vendure-admin-ui-marketing.metadata.json +1 -1
  104. package/order/vendure-admin-ui-order.metadata.json +1 -1
  105. package/package.json +5 -5
  106. package/static/i18n-messages/cs.json +9 -0
  107. package/static/i18n-messages/de.json +9 -0
  108. package/static/i18n-messages/en.json +10 -1
  109. package/static/i18n-messages/es.json +9 -0
  110. package/static/i18n-messages/fr.json +9 -0
  111. package/static/i18n-messages/it.json +9 -0
  112. package/static/i18n-messages/pl.json +9 -0
  113. package/static/i18n-messages/pt_BR.json +9 -0
  114. package/static/i18n-messages/pt_PT.json +9 -0
  115. package/static/i18n-messages/ru.json +9 -0
  116. package/static/i18n-messages/uk.json +9 -0
  117. package/static/i18n-messages/zh_Hans.json +9 -0
  118. package/static/i18n-messages/zh_Hant.json +9 -0
  119. package/static/styles/global/_forms.scss +4 -5
  120. package/static/styles/global/_overrides.scss +10 -0
  121. package/static/styles/theme/default.scss +13 -1
  122. package/static/theme.min.css +1 -1
  123. 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
@@ -4589,6 +4611,31 @@ var LogicalOperator;
4589
4611
  * Permissions for administrators and customers. Used to control access to
4590
4612
  * GraphQL resolvers via the {@link Allow} decorator.
4591
4613
  *
4614
+ * ## Understanding Permission.Owner
4615
+ *
4616
+ * `Permission.Owner` is a special permission which is used in some of the Vendure resolvers to indicate that that resolver should only
4617
+ * be accessible to the "owner" of that resource.
4618
+ *
4619
+ * For example, the Shop API `activeCustomer` query resolver should only return the Customer object for the "owner" of that Customer, i.e.
4620
+ * based on the activeUserId of the current session. As a result, the resolver code looks like this:
4621
+ *
4622
+ * @example
4623
+ * ```TypeScript
4624
+ * \@Query()
4625
+ * \@Allow(Permission.Owner)
4626
+ * async activeCustomer(\@Ctx() ctx: RequestContext): Promise<Customer | undefined> {
4627
+ * const userId = ctx.activeUserId;
4628
+ * if (userId) {
4629
+ * return this.customerService.findOneByUserId(ctx, userId);
4630
+ * }
4631
+ * }
4632
+ * ```
4633
+ *
4634
+ * Here we can see that the "ownership" must be enforced by custom logic inside the resolver. Since "ownership" cannot be defined generally
4635
+ * nor statically encoded at build-time, any resolvers using `Permission.Owner` **must** include logic to enforce that only the owner
4636
+ * of the resource has access. If not, then it is the equivalent of using `Permission.Public`.
4637
+ *
4638
+ *
4592
4639
  * @docsCategory common
4593
4640
  */
4594
4641
  var Permission;
@@ -6359,6 +6406,7 @@ class AppShellComponent {
6359
6406
  this.modalService = modalService;
6360
6407
  this.localStorageService = localStorageService;
6361
6408
  this.availableLanguages = [];
6409
+ this.hideVendureBranding = getAppConfig().hideVendureBranding;
6362
6410
  }
6363
6411
  ngOnInit() {
6364
6412
  this.userName$ = this.dataService.client
@@ -6404,8 +6452,8 @@ class AppShellComponent {
6404
6452
  AppShellComponent.decorators = [
6405
6453
  { type: Component, args: [{
6406
6454
  selector: 'vdr-app-shell',
6407
- 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",
6408
- 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"]
6409
6457
  },] }
6410
6458
  ];
6411
6459
  AppShellComponent.ctorParameters = () => [
@@ -7379,194 +7427,260 @@ UserMenuComponent.propDecorators = {
7379
7427
 
7380
7428
  // tslint:disable
7381
7429
  const result = {
7382
- possibleTypes: {
7383
- AddFulfillmentToOrderResult: [
7384
- 'Fulfillment',
7385
- 'EmptyOrderLineSelectionError',
7386
- 'ItemsAlreadyFulfilledError',
7387
- 'InsufficientStockOnHandError',
7388
- 'InvalidFulfillmentHandlerError',
7389
- 'FulfillmentStateTransitionError',
7390
- 'CreateFulfillmentError',
7430
+ "possibleTypes": {
7431
+ "AddFulfillmentToOrderResult": [
7432
+ "Fulfillment",
7433
+ "EmptyOrderLineSelectionError",
7434
+ "ItemsAlreadyFulfilledError",
7435
+ "InsufficientStockOnHandError",
7436
+ "InvalidFulfillmentHandlerError",
7437
+ "FulfillmentStateTransitionError",
7438
+ "CreateFulfillmentError"
7391
7439
  ],
7392
- AddManualPaymentToOrderResult: ['Order', 'ManualPaymentStateError'],
7393
- AuthenticationResult: ['CurrentUser', 'InvalidCredentialsError'],
7394
- CancelOrderResult: [
7395
- 'Order',
7396
- 'EmptyOrderLineSelectionError',
7397
- 'QuantityTooGreatError',
7398
- 'MultipleOrderError',
7399
- 'CancelActiveOrderError',
7400
- 'OrderStateTransitionError',
7440
+ "AddManualPaymentToOrderResult": [
7441
+ "Order",
7442
+ "ManualPaymentStateError"
7401
7443
  ],
7402
- CreateAssetResult: ['Asset', 'MimeTypeError'],
7403
- CreateChannelResult: ['Channel', 'LanguageNotAvailableError'],
7404
- CreateCustomerResult: ['Customer', 'EmailAddressConflictError'],
7405
- CreatePromotionResult: ['Promotion', 'MissingConditionsError'],
7406
- CustomField: [
7407
- 'BooleanCustomFieldConfig',
7408
- 'DateTimeCustomFieldConfig',
7409
- 'FloatCustomFieldConfig',
7410
- 'IntCustomFieldConfig',
7411
- 'LocaleStringCustomFieldConfig',
7412
- 'RelationCustomFieldConfig',
7413
- 'StringCustomFieldConfig',
7414
- 'TextCustomFieldConfig',
7444
+ "AuthenticationResult": [
7445
+ "CurrentUser",
7446
+ "InvalidCredentialsError"
7415
7447
  ],
7416
- CustomFieldConfig: [
7417
- 'StringCustomFieldConfig',
7418
- 'LocaleStringCustomFieldConfig',
7419
- 'IntCustomFieldConfig',
7420
- 'FloatCustomFieldConfig',
7421
- 'BooleanCustomFieldConfig',
7422
- 'DateTimeCustomFieldConfig',
7423
- 'RelationCustomFieldConfig',
7424
- 'TextCustomFieldConfig',
7448
+ "CancelOrderResult": [
7449
+ "Order",
7450
+ "EmptyOrderLineSelectionError",
7451
+ "QuantityTooGreatError",
7452
+ "MultipleOrderError",
7453
+ "CancelActiveOrderError",
7454
+ "OrderStateTransitionError"
7425
7455
  ],
7426
- ErrorResult: [
7427
- 'AlreadyRefundedError',
7428
- 'CancelActiveOrderError',
7429
- 'ChannelDefaultLanguageError',
7430
- 'CouponCodeExpiredError',
7431
- 'CouponCodeInvalidError',
7432
- 'CouponCodeLimitError',
7433
- 'CreateFulfillmentError',
7434
- 'EmailAddressConflictError',
7435
- 'EmptyOrderLineSelectionError',
7436
- 'FulfillmentStateTransitionError',
7437
- 'InsufficientStockError',
7438
- 'InsufficientStockOnHandError',
7439
- 'InvalidCredentialsError',
7440
- 'InvalidFulfillmentHandlerError',
7441
- 'ItemsAlreadyFulfilledError',
7442
- 'LanguageNotAvailableError',
7443
- 'ManualPaymentStateError',
7444
- 'MimeTypeError',
7445
- 'MissingConditionsError',
7446
- 'MultipleOrderError',
7447
- 'NativeAuthStrategyError',
7448
- 'NegativeQuantityError',
7449
- 'NoChangesSpecifiedError',
7450
- 'NothingToRefundError',
7451
- 'OrderLimitError',
7452
- 'OrderModificationStateError',
7453
- 'OrderStateTransitionError',
7454
- 'PaymentMethodMissingError',
7455
- 'PaymentOrderMismatchError',
7456
- 'PaymentStateTransitionError',
7457
- 'ProductOptionInUseError',
7458
- 'QuantityTooGreatError',
7459
- 'RefundOrderStateError',
7460
- 'RefundPaymentIdMissingError',
7461
- 'RefundStateTransitionError',
7462
- 'SettlePaymentError',
7456
+ "CreateAssetResult": [
7457
+ "Asset",
7458
+ "MimeTypeError"
7463
7459
  ],
7464
- ModifyOrderResult: [
7465
- 'Order',
7466
- 'NoChangesSpecifiedError',
7467
- 'OrderModificationStateError',
7468
- 'PaymentMethodMissingError',
7469
- 'RefundPaymentIdMissingError',
7470
- 'OrderLimitError',
7471
- 'NegativeQuantityError',
7472
- 'InsufficientStockError',
7473
- 'CouponCodeExpiredError',
7474
- 'CouponCodeInvalidError',
7475
- 'CouponCodeLimitError',
7460
+ "CreateChannelResult": [
7461
+ "Channel",
7462
+ "LanguageNotAvailableError"
7476
7463
  ],
7477
- NativeAuthenticationResult: ['CurrentUser', 'InvalidCredentialsError', 'NativeAuthStrategyError'],
7478
- Node: [
7479
- 'Address',
7480
- 'Administrator',
7481
- 'Allocation',
7482
- 'Asset',
7483
- 'AuthenticationMethod',
7484
- 'Cancellation',
7485
- 'Channel',
7486
- 'Collection',
7487
- 'Country',
7488
- 'Customer',
7489
- 'CustomerGroup',
7490
- 'Facet',
7491
- 'FacetValue',
7492
- 'Fulfillment',
7493
- 'HistoryEntry',
7494
- 'Job',
7495
- 'Order',
7496
- 'OrderItem',
7497
- 'OrderLine',
7498
- 'OrderModification',
7499
- 'Payment',
7500
- 'PaymentMethod',
7501
- 'Product',
7502
- 'ProductOption',
7503
- 'ProductOptionGroup',
7504
- 'ProductVariant',
7505
- 'Promotion',
7506
- 'Refund',
7507
- 'Release',
7508
- 'Return',
7509
- 'Role',
7510
- 'Sale',
7511
- 'ShippingMethod',
7512
- 'StockAdjustment',
7513
- 'Surcharge',
7514
- 'Tag',
7515
- 'TaxCategory',
7516
- 'TaxRate',
7517
- 'User',
7518
- 'Zone',
7464
+ "CreateCustomerResult": [
7465
+ "Customer",
7466
+ "EmailAddressConflictError"
7519
7467
  ],
7520
- PaginatedList: [
7521
- 'AdministratorList',
7522
- 'AssetList',
7523
- 'CollectionList',
7524
- 'CountryList',
7525
- 'CustomerGroupList',
7526
- 'CustomerList',
7527
- 'FacetList',
7528
- 'HistoryEntryList',
7529
- 'JobList',
7530
- 'OrderList',
7531
- 'PaymentMethodList',
7532
- 'ProductList',
7533
- 'ProductVariantList',
7534
- 'PromotionList',
7535
- 'RoleList',
7536
- 'ShippingMethodList',
7537
- 'TagList',
7538
- 'TaxRateList',
7468
+ "CreatePromotionResult": [
7469
+ "Promotion",
7470
+ "MissingConditionsError"
7539
7471
  ],
7540
- RefundOrderResult: [
7541
- 'Refund',
7542
- 'QuantityTooGreatError',
7543
- 'NothingToRefundError',
7544
- 'OrderStateTransitionError',
7545
- 'MultipleOrderError',
7546
- 'PaymentOrderMismatchError',
7547
- 'RefundOrderStateError',
7548
- 'AlreadyRefundedError',
7549
- 'RefundStateTransitionError',
7472
+ "CustomField": [
7473
+ "BooleanCustomFieldConfig",
7474
+ "DateTimeCustomFieldConfig",
7475
+ "FloatCustomFieldConfig",
7476
+ "IntCustomFieldConfig",
7477
+ "LocaleStringCustomFieldConfig",
7478
+ "RelationCustomFieldConfig",
7479
+ "StringCustomFieldConfig",
7480
+ "TextCustomFieldConfig"
7550
7481
  ],
7551
- RemoveOptionGroupFromProductResult: ['Product', 'ProductOptionInUseError'],
7552
- SearchResultPrice: ['PriceRange', 'SinglePrice'],
7553
- SettlePaymentResult: [
7554
- 'Payment',
7555
- 'SettlePaymentError',
7556
- 'PaymentStateTransitionError',
7557
- 'OrderStateTransitionError',
7482
+ "CustomFieldConfig": [
7483
+ "StringCustomFieldConfig",
7484
+ "LocaleStringCustomFieldConfig",
7485
+ "IntCustomFieldConfig",
7486
+ "FloatCustomFieldConfig",
7487
+ "BooleanCustomFieldConfig",
7488
+ "DateTimeCustomFieldConfig",
7489
+ "RelationCustomFieldConfig",
7490
+ "TextCustomFieldConfig"
7558
7491
  ],
7559
- SettleRefundResult: ['Refund', 'RefundStateTransitionError'],
7560
- StockMovement: ['Allocation', 'Cancellation', 'Release', 'Return', 'Sale', 'StockAdjustment'],
7561
- StockMovementItem: ['StockAdjustment', 'Allocation', 'Sale', 'Cancellation', 'Return', 'Release'],
7562
- TransitionFulfillmentToStateResult: ['Fulfillment', 'FulfillmentStateTransitionError'],
7563
- TransitionOrderToStateResult: ['Order', 'OrderStateTransitionError'],
7564
- TransitionPaymentToStateResult: ['Payment', 'PaymentStateTransitionError'],
7565
- UpdateChannelResult: ['Channel', 'LanguageNotAvailableError'],
7566
- UpdateCustomerResult: ['Customer', 'EmailAddressConflictError'],
7567
- UpdateGlobalSettingsResult: ['GlobalSettings', 'ChannelDefaultLanguageError'],
7568
- UpdatePromotionResult: ['Promotion', 'MissingConditionsError'],
7569
- },
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
+ }
7570
7684
  };
7571
7685
 
7572
7686
  // Allows the introspectionResult to be imported as a named symbol
@@ -7631,7 +7745,7 @@ function getClientDefaults(localStorageService) {
7631
7745
  };
7632
7746
  }
7633
7747
 
7634
- const ɵ0$2 = (_, args, { cache }) => {
7748
+ const ɵ0$3 = (_, args, { cache }) => {
7635
7749
  return updateRequestsInFlight(cache, 1);
7636
7750
  }, ɵ1 = (_, args, { cache }) => {
7637
7751
  return updateRequestsInFlight(cache, -1);
@@ -7721,7 +7835,7 @@ const ɵ0$2 = (_, args, { cache }) => {
7721
7835
  };
7722
7836
  const clientResolvers = {
7723
7837
  Mutation: {
7724
- requestStarted: ɵ0$2,
7838
+ requestStarted: ɵ0$3,
7725
7839
  requestCompleted: ɵ1,
7726
7840
  setAsLoggedIn: ɵ2,
7727
7841
  setAsLoggedOut: ɵ3,
@@ -8145,7 +8259,7 @@ function createApollo(localStorageService, fetchAdapter, injector) {
8145
8259
  resolvers: clientResolvers,
8146
8260
  };
8147
8261
  }
8148
- const ɵ0$1 = initializeServerConfigService;
8262
+ const ɵ0$2 = initializeServerConfigService;
8149
8263
  /**
8150
8264
  * The DataModule is responsible for all API calls *and* serves as the source of truth for global app
8151
8265
  * state via the apollo-link-state package.
@@ -8171,7 +8285,7 @@ DataModule.decorators = [
8171
8285
  {
8172
8286
  provide: APP_INITIALIZER,
8173
8287
  multi: true,
8174
- useFactory: ɵ0$1,
8288
+ useFactory: ɵ0$2,
8175
8289
  deps: [ServerConfigService],
8176
8290
  },
8177
8291
  ],
@@ -8449,1834 +8563,2121 @@ JsonEditorFormInputComponent.propDecorators = {
8449
8563
  };
8450
8564
 
8451
8565
  /**
8452
- * @description
8453
- * An input for monetary values. Should be used with `int` type fields.
8454
- *
8455
- * @docsCategory custom-input-components
8456
- * @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.
8457
8569
  */
8458
- class CurrencyFormInputComponent {
8459
- constructor(dataService) {
8460
- this.dataService = dataService;
8461
- this.currencyCode$ = this.dataService.settings
8462
- .getActiveChannel()
8463
- .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;
8464
8576
  }
8465
8577
  }
8466
- CurrencyFormInputComponent.id = 'currency-form-input';
8467
- CurrencyFormInputComponent.decorators = [
8468
- { type: Component, args: [{
8469
- selector: 'vdr-currency-form-input',
8470
- template: "<vdr-currency-input\r\n [formControl]=\"formControl\"\r\n [readonly]=\"readonly\"\r\n [currencyCode]=\"currencyCode$ | async\"\r\n></vdr-currency-input>\r\n",
8471
- changeDetection: ChangeDetectionStrategy.OnPush,
8472
- styles: [""]
8473
- },] }
8474
- ];
8475
- CurrencyFormInputComponent.ctorParameters = () => [
8476
- { type: DataService }
8477
- ];
8478
- CurrencyFormInputComponent.propDecorators = {
8479
- readonly: [{ type: Input }]
8480
- };
8481
-
8578
+ function encodeConfigArgValue(value) {
8579
+ return Array.isArray(value) ? JSON.stringify(value) : (value !== null && value !== void 0 ? value : '').toString();
8580
+ }
8482
8581
  /**
8483
- * @description
8484
- * Allows the selection of a Customer via an autocomplete select input.
8485
- * Should be used with `ID` type fields which represent Customer IDs.
8486
- *
8487
- * @docsCategory custom-input-components
8488
- * @docsPage default-inputs
8582
+ * Creates an empty ConfigurableOperation object based on the definition.
8489
8583
  */
8490
- class CustomerGroupFormInputComponent {
8491
- constructor(dataService) {
8492
- this.dataService = dataService;
8493
- }
8494
- ngOnInit() {
8495
- this.customerGroups$ = this.dataService.customer
8496
- .getCustomerGroupList({
8497
- take: 1000,
8498
- })
8499
- .mapSingle(res => res.customerGroups.items)
8500
- .pipe(startWith([]));
8501
- }
8502
- selectGroup(group) {
8503
- this.formControl.setValue(group.id);
8504
- }
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
+ }) });
8505
8588
  }
8506
- CustomerGroupFormInputComponent.id = 'customer-group-form-input';
8507
- CustomerGroupFormInputComponent.decorators = [
8508
- { type: Component, args: [{
8509
- selector: 'vdr-customer-group-form-input',
8510
- 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",
8511
- changeDetection: ChangeDetectionStrategy.OnPush,
8512
- styles: [""]
8513
- },] }
8514
- ];
8515
- CustomerGroupFormInputComponent.ctorParameters = () => [
8516
- { type: DataService }
8517
- ];
8518
- CustomerGroupFormInputComponent.propDecorators = {
8519
- readonly: [{ type: Input }]
8520
- };
8521
-
8522
8589
  /**
8523
- * @description
8524
- * Allows selection of a datetime. Default input for `datetime` type fields.
8525
- *
8526
- * @docsCategory custom-input-components
8527
- * @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
+ * ```
8528
8608
  */
8529
- class DateFormInputComponent {
8530
- get min() {
8531
- var _a;
8532
- 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;
8533
8623
  }
8534
- get max() {
8535
- var _a;
8536
- 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;
8537
8626
  }
8538
- get yearRange() {
8539
- var _a;
8540
- 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
+ }
8541
8632
  }
8633
+ return true;
8542
8634
  }
8543
- DateFormInputComponent.id = 'date-form-input';
8544
- DateFormInputComponent.decorators = [
8545
- { type: Component, args: [{
8546
- selector: 'vdr-date-form-input',
8547
- 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",
8548
- changeDetection: ChangeDetectionStrategy.OnPush,
8549
- styles: [""]
8550
- },] }
8551
- ];
8552
- DateFormInputComponent.propDecorators = {
8553
- readonly: [{ type: Input }]
8554
- };
8555
-
8556
8635
  /**
8557
- * @description
8558
- * Allows the selection of multiple FacetValues via an autocomplete select input.
8559
- * Should be used with `ID` type **list** fields which represent FacetValue IDs.
8560
- *
8561
- * @docsCategory custom-input-components
8562
- * @docsPage default-inputs
8636
+ * Returns a default value based on the type of the config arg.
8563
8637
  */
8564
- class FacetValueFormInputComponent {
8565
- constructor(dataService) {
8566
- this.dataService = dataService;
8567
- this.isListInput = true;
8638
+ function getDefaultConfigArgValue(arg) {
8639
+ if (arg.list) {
8640
+ return [];
8568
8641
  }
8569
- ngOnInit() {
8570
- this.facets$ = this.dataService.facet
8571
- .getAllFacets()
8572
- .mapSingle(data => data.facets.items)
8573
- .pipe(shareReplay(1));
8642
+ if (arg.defaultValue) {
8643
+ return arg.defaultValue;
8574
8644
  }
8575
- }
8576
- FacetValueFormInputComponent.id = 'facet-value-form-input';
8577
- FacetValueFormInputComponent.decorators = [
8578
- { type: Component, args: [{
8579
- selector: 'vdr-facet-value-form-input',
8580
- 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",
8581
- changeDetection: ChangeDetectionStrategy.OnPush,
8582
- styles: [""]
8583
- },] }
8584
- ];
8585
- FacetValueFormInputComponent.ctorParameters = () => [
8586
- { type: DataService }
8587
- ];
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
+ }
8588
8659
 
8589
8660
  /**
8590
- * @description
8591
- * Displays a number input. Default input for `int` and `float` type fields.
8592
- *
8593
- * @docsCategory custom-input-components
8594
- * @docsPage default-inputs
8661
+ * Interpolates the description of an ConfigurableOperation with the given values.
8595
8662
  */
8596
- class NumberFormInputComponent {
8597
- get prefix() {
8598
- var _a;
8599
- 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 '';
8600
8666
  }
8601
- get suffix() {
8602
- var _a;
8603
- 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);
8604
8699
  }
8605
- get min() {
8606
- var _a;
8607
- 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
+ }
8608
8707
  }
8609
- get max() {
8610
- var _a;
8611
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.max) || this.config.max;
8708
+ ngOnInit() {
8709
+ this.positionChange$ = this.positionChangeSubject.asObservable();
8612
8710
  }
8613
- 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) {
8614
8747
  var _a;
8615
- 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;
8616
8789
  }
8617
8790
  }
8618
- NumberFormInputComponent.id = 'number-form-input';
8619
- NumberFormInputComponent.decorators = [
8791
+ ConfigurableInputComponent.decorators = [
8620
8792
  { type: Component, args: [{
8621
- selector: 'vdr-number-form-input',
8622
- 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",
8623
8795
  changeDetection: ChangeDetectionStrategy.OnPush,
8624
- 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"]
8625
8809
  },] }
8626
8810
  ];
8627
- NumberFormInputComponent.propDecorators = {
8628
- 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 }]
8629
8818
  };
8630
8819
 
8631
8820
  /**
8632
8821
  * @description
8633
- * Displays a password text input. Should be used with `string` type fields.
8634
- *
8635
- * @docsCategory custom-input-components
8636
- * @docsPage default-inputs
8637
- */
8638
- class PasswordFormInputComponent {
8639
- }
8640
- PasswordFormInputComponent.id = 'password-form-input';
8641
- PasswordFormInputComponent.decorators = [
8642
- { type: Component, args: [{
8643
- selector: 'vdr-password-form-input',
8644
- template: "<input\r\n type=\"password\"\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n/>\r\n",
8645
- changeDetection: ChangeDetectionStrategy.OnPush,
8646
- styles: [""]
8647
- },] }
8648
- ];
8649
-
8650
- /**
8651
- * @description
8652
- * Allows the selection of multiple ProductVariants via an autocomplete select input.
8653
- * Should be used with `ID` type **list** fields which represent ProductVariant IDs.
8822
+ * A special input used to display the "Combination mode" AND/OR toggle.
8654
8823
  *
8655
8824
  * @docsCategory custom-input-components
8656
8825
  * @docsPage default-inputs
8657
8826
  */
8658
- class ProductSelectorFormInputComponent {
8659
- constructor(dataService) {
8660
- this.dataService = dataService;
8661
- this.isListInput = true;
8827
+ class CombinationModeFormInputComponent {
8828
+ constructor(configurableInputComponent) {
8829
+ this.configurableInputComponent = configurableInputComponent;
8662
8830
  }
8663
8831
  ngOnInit() {
8664
- this.formControl.setValidators([
8665
- control => {
8666
- if (!control.value || !control.value.length) {
8667
- return {
8668
- atLeastOne: { length: control.value.length },
8669
- };
8670
- }
8671
- return null;
8672
- },
8673
- ]);
8674
- this.selection$ = this.formControl.valueChanges.pipe(startWith(this.formControl.value), switchMap(value => {
8675
- if (Array.isArray(value) && 0 < value.length) {
8676
- return forkJoin(value.map(id => this.dataService.product
8677
- .getProductVariant(id)
8678
- .mapSingle(data => data.productVariant)));
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 });
8679
8838
  }
8680
- return of([]);
8681
- }), map(variants => variants.filter(notNullOrUndefined)));
8839
+ }));
8682
8840
  }
8683
- addProductVariant(product) {
8684
- const value = this.formControl.value;
8685
- this.formControl.setValue([...new Set([...value, product.productVariantId])]);
8841
+ setCombinationModeAnd() {
8842
+ this.formControl.setValue(true);
8686
8843
  }
8687
- removeProductVariant(id) {
8688
- const value = this.formControl.value;
8689
- this.formControl.setValue(value.filter(_id => _id !== id));
8844
+ setCombinationModeOr() {
8845
+ this.formControl.setValue(false);
8690
8846
  }
8691
8847
  }
8692
- ProductSelectorFormInputComponent.id = 'product-selector-form-input';
8693
- ProductSelectorFormInputComponent.decorators = [
8848
+ CombinationModeFormInputComponent.id = 'combination-mode-form-input';
8849
+ CombinationModeFormInputComponent.decorators = [
8694
8850
  { type: Component, args: [{
8695
- selector: 'vdr-product-selector-form-input',
8696
- 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",
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",
8697
8853
  changeDetection: ChangeDetectionStrategy.OnPush,
8698
- 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"]
8854
+ styles: [".mode-select{text-transform:uppercase}\n"]
8699
8855
  },] }
8700
8856
  ];
8701
- ProductSelectorFormInputComponent.ctorParameters = () => [
8702
- { type: DataService }
8857
+ CombinationModeFormInputComponent.ctorParameters = () => [
8858
+ { type: ConfigurableInputComponent, decorators: [{ type: Optional }] }
8703
8859
  ];
8704
8860
 
8705
8861
  /**
8706
8862
  * @description
8707
- * The default input component for `relation` type custom fields. Allows the selection
8708
- * of a ProductVariant, Product, Customer or Asset. For other entity types, a custom
8709
- * implementation will need to be defined. See {@link registerFormInputComponent}.
8863
+ * An input for monetary values. Should be used with `int` type fields.
8710
8864
  *
8711
8865
  * @docsCategory custom-input-components
8712
8866
  * @docsPage default-inputs
8713
8867
  */
8714
- class RelationFormInputComponent {
8868
+ class CurrencyFormInputComponent {
8869
+ constructor(dataService) {
8870
+ this.dataService = dataService;
8871
+ this.currencyCode$ = this.dataService.settings
8872
+ .getActiveChannel()
8873
+ .mapStream(data => data.activeChannel.currencyCode);
8874
+ }
8715
8875
  }
8716
- RelationFormInputComponent.id = 'relation-form-input';
8717
- RelationFormInputComponent.decorators = [
8876
+ CurrencyFormInputComponent.id = 'currency-form-input';
8877
+ CurrencyFormInputComponent.decorators = [
8718
8878
  { type: Component, args: [{
8719
- selector: 'vdr-relation-form-input',
8720
- 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",
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",
8721
8881
  changeDetection: ChangeDetectionStrategy.OnPush,
8722
- styles: [":host{display:block;background-color:var(--color-component-bg-200);padding:3px}\n"]
8882
+ styles: [""]
8723
8883
  },] }
8724
8884
  ];
8725
- RelationFormInputComponent.propDecorators = {
8885
+ CurrencyFormInputComponent.ctorParameters = () => [
8886
+ { type: DataService }
8887
+ ];
8888
+ CurrencyFormInputComponent.propDecorators = {
8726
8889
  readonly: [{ type: Input }]
8727
8890
  };
8728
8891
 
8729
8892
  /**
8730
8893
  * @description
8731
- * Uses the {@link RichTextEditorComponent} as in input for `text` type fields.
8894
+ * Allows the selection of a Customer via an autocomplete select input.
8895
+ * Should be used with `ID` type fields which represent Customer IDs.
8732
8896
  *
8733
8897
  * @docsCategory custom-input-components
8734
8898
  * @docsPage default-inputs
8735
8899
  */
8736
- class RichTextFormInputComponent {
8737
- }
8738
- RichTextFormInputComponent.id = 'rich-text-form-input';
8739
- RichTextFormInputComponent.decorators = [
8740
- { type: Component, args: [{
8741
- selector: 'vdr-rich-text-form-input',
8742
- template: "<vdr-rich-text-editor\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n></vdr-rich-text-editor>\r\n",
8743
- changeDetection: ChangeDetectionStrategy.OnPush,
8744
- styles: [":host textarea{resize:both;height:6rem;width:100%}\n"]
8745
- },] }
8746
- ];
8747
-
8748
- /**
8749
- * @description
8750
- * Uses a select input to allow the selection of a string value. Should be used with
8751
- * `string` type fields with options.
8752
- *
8753
- * @docsCategory custom-input-components
8754
- * @docsPage default-inputs
8755
- */
8756
- class SelectFormInputComponent {
8757
- get options() {
8758
- var _a;
8759
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.options) || this.config.options;
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);
8760
8914
  }
8761
8915
  }
8762
- SelectFormInputComponent.id = 'select-form-input';
8763
- SelectFormInputComponent.decorators = [
8916
+ CustomerGroupFormInputComponent.id = 'customer-group-form-input';
8917
+ CustomerGroupFormInputComponent.decorators = [
8764
8918
  { type: Component, args: [{
8765
- selector: 'vdr-select-form-input',
8766
- 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",
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",
8767
8921
  changeDetection: ChangeDetectionStrategy.OnPush,
8768
- styles: ["select{width:100%}\n"]
8922
+ styles: [""]
8769
8923
  },] }
8770
8924
  ];
8771
- SelectFormInputComponent.propDecorators = {
8925
+ CustomerGroupFormInputComponent.ctorParameters = () => [
8926
+ { type: DataService }
8927
+ ];
8928
+ CustomerGroupFormInputComponent.propDecorators = {
8772
8929
  readonly: [{ type: Input }]
8773
8930
  };
8774
8931
 
8775
8932
  /**
8776
8933
  * @description
8777
- * Uses a regular text form input. This is the default input for `string` and `localeString` type fields.
8934
+ * Allows selection of a datetime. Default input for `datetime` type fields.
8778
8935
  *
8779
8936
  * @docsCategory custom-input-components
8780
8937
  * @docsPage default-inputs
8781
8938
  */
8782
- class TextFormInputComponent {
8783
- get prefix() {
8939
+ class DateFormInputComponent {
8940
+ get min() {
8784
8941
  var _a;
8785
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.prefix) || this.config.prefix;
8942
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.min) || this.config.min;
8786
8943
  }
8787
- get suffix() {
8944
+ get max() {
8788
8945
  var _a;
8789
- return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.suffix) || this.config.suffix;
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;
8790
8951
  }
8791
8952
  }
8792
- TextFormInputComponent.id = 'text-form-input';
8793
- TextFormInputComponent.decorators = [
8953
+ DateFormInputComponent.id = 'date-form-input';
8954
+ DateFormInputComponent.decorators = [
8794
8955
  { type: Component, args: [{
8795
- selector: 'vdr-text-form-input',
8796
- 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",
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",
8797
8958
  changeDetection: ChangeDetectionStrategy.OnPush,
8798
- styles: ["input{width:100%}\n"]
8959
+ styles: [""]
8799
8960
  },] }
8800
- ];
8961
+ ];
8962
+ DateFormInputComponent.propDecorators = {
8963
+ readonly: [{ type: Input }]
8964
+ };
8801
8965
 
8802
8966
  /**
8803
8967
  * @description
8804
- * Uses textarea form input. This is the default input for `text` type fields.
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.
8805
8970
  *
8806
8971
  * @docsCategory custom-input-components
8807
8972
  * @docsPage default-inputs
8808
8973
  */
8809
- class TextareaFormInputComponent {
8810
- get spellcheck() {
8811
- return this.config.spellcheck === true;
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));
8812
8984
  }
8813
8985
  }
8814
- TextareaFormInputComponent.id = 'textarea-form-input';
8815
- TextareaFormInputComponent.decorators = [
8986
+ FacetValueFormInputComponent.id = 'facet-value-form-input';
8987
+ FacetValueFormInputComponent.decorators = [
8816
8988
  { type: Component, args: [{
8817
- selector: 'vdr-textarea-form-input',
8818
- template: "<textarea [spellcheck]=\"spellcheck\" autocomplete=\"off\" autocorrect=\"off\"\r\n [readonly]=\"readonly\"\r\n [formControl]=\"formControl\"\r\n></textarea>\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",
8819
8991
  changeDetection: ChangeDetectionStrategy.OnPush,
8820
- styles: [":host textarea{resize:both;height:6rem;width:100%}\n"]
8992
+ styles: [""]
8821
8993
  },] }
8994
+ ];
8995
+ FacetValueFormInputComponent.ctorParameters = () => [
8996
+ { type: DataService }
8822
8997
  ];
8823
8998
 
8824
- const defaultFormInputs = [
8825
- BooleanFormInputComponent,
8826
- CurrencyFormInputComponent,
8827
- DateFormInputComponent,
8828
- FacetValueFormInputComponent,
8829
- NumberFormInputComponent,
8830
- SelectFormInputComponent,
8831
- TextFormInputComponent,
8832
- ProductSelectorFormInputComponent,
8833
- CustomerGroupFormInputComponent,
8834
- PasswordFormInputComponent,
8835
- RelationFormInputComponent,
8836
- TextareaFormInputComponent,
8837
- RichTextFormInputComponent,
8838
- JsonEditorFormInputComponent,
8839
- ];
8840
8999
  /**
8841
9000
  * @description
8842
- * Registers a custom FormInputComponent which can be used to control the argument inputs
8843
- * of a {@link ConfigurableOperationDef} (e.g. CollectionFilter, ShippingMethod etc) or for
8844
- * a custom field.
8845
- *
8846
- * @example
8847
- * ```TypeScript
8848
- * \@NgModule({
8849
- * imports: [SharedModule],
8850
- * declarations: [MyCustomFieldControl],
8851
- * providers: [
8852
- * registerFormInputComponent('my-custom-input', MyCustomFieldControl),
8853
- * ],
8854
- * })
8855
- * export class MyUiExtensionModule {}
8856
- * ```
8857
- *
8858
- * This input component can then be used in a custom field:
8859
- *
8860
- * @example
8861
- * ```TypeScript
8862
- * const config = {
8863
- * // ...
8864
- * customFields: {
8865
- * ProductVariant: [
8866
- * {
8867
- * name: 'rrp',
8868
- * type: 'int',
8869
- * ui: { component: 'my-custom-input' },
8870
- * },
8871
- * ]
8872
- * }
8873
- * }
8874
- * ```
8875
- *
8876
- * or with an argument of a {@link ConfigurableOperationDef}:
8877
- *
8878
- * @example
8879
- * ```TypeScript
8880
- * args: {
8881
- * rrp: { type: 'int', ui: { component: 'my-custom-input' } },
8882
- * }
8883
- * ```
9001
+ * Displays a number input. Default input for `int` and `float` type fields.
8884
9002
  *
8885
9003
  * @docsCategory custom-input-components
9004
+ * @docsPage default-inputs
8886
9005
  */
8887
- function registerFormInputComponent(id, component) {
8888
- return {
8889
- provide: APP_INITIALIZER,
8890
- multi: true,
8891
- useFactory: (registry) => () => {
8892
- registry.registerInputComponent(id, component);
8893
- },
8894
- deps: [ComponentRegistryService],
8895
- };
9006
+ class NumberFormInputComponent {
9007
+ get prefix() {
9008
+ var _a;
9009
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.prefix) || this.config.prefix;
9010
+ }
9011
+ get suffix() {
9012
+ var _a;
9013
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.suffix) || this.config.suffix;
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
+ }
8896
9027
  }
9028
+ NumberFormInputComponent.id = 'number-form-input';
9029
+ NumberFormInputComponent.decorators = [
9030
+ { type: Component, args: [{
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",
9033
+ changeDetection: ChangeDetectionStrategy.OnPush,
9034
+ styles: [""]
9035
+ },] }
9036
+ ];
9037
+ NumberFormInputComponent.propDecorators = {
9038
+ readonly: [{ type: Input }]
9039
+ };
9040
+
8897
9041
  /**
8898
9042
  * @description
8899
- * **Deprecated** use `registerFormInputComponent()` in combination with the customField `ui` config instead.
8900
- *
8901
- * Registers a custom component to act as the form input control for the given custom field.
8902
- * This should be used in the NgModule `providers` array of your ui extension module.
8903
- *
8904
- * @example
8905
- * ```TypeScript
8906
- * \@NgModule({
8907
- * imports: [SharedModule],
8908
- * declarations: [MyCustomFieldControl],
8909
- * providers: [
8910
- * registerCustomFieldComponent('Product', 'someCustomField', MyCustomFieldControl),
8911
- * ],
8912
- * })
8913
- * export class MyUiExtensionModule {}
8914
- * ```
8915
- *
8916
- * @deprecated use `registerFormInputComponent()` in combination with the customField `ui` config instead.
9043
+ * Displays a password text input. Should be used with `string` type fields.
8917
9044
  *
8918
9045
  * @docsCategory custom-input-components
9046
+ * @docsPage default-inputs
8919
9047
  */
8920
- function registerCustomFieldComponent(entity, fieldName, component) {
8921
- return {
8922
- provide: APP_INITIALIZER,
8923
- multi: true,
8924
- useFactory: (customFieldComponentService) => () => {
8925
- customFieldComponentService.registerCustomFieldComponent(entity, fieldName, component);
8926
- },
8927
- deps: [CustomFieldComponentService],
8928
- };
9048
+ class PasswordFormInputComponent {
8929
9049
  }
9050
+ PasswordFormInputComponent.id = 'password-form-input';
9051
+ PasswordFormInputComponent.decorators = [
9052
+ { type: Component, args: [{
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",
9055
+ changeDetection: ChangeDetectionStrategy.OnPush,
9056
+ styles: [""]
9057
+ },] }
9058
+ ];
9059
+
8930
9060
  /**
8931
- * Registers the default form input components.
9061
+ * @description
9062
+ * A helper class used to manage selection of list items. Supports multiple selection via
9063
+ * cmd/ctrl/shift key.
8932
9064
  */
8933
- function registerDefaultFormInputs() {
8934
- return defaultFormInputs.map(cmp => registerFormInputComponent(cmp.id, cmp));
8935
- }
8936
-
8937
- class ActionBarItemsComponent {
8938
- constructor(navBuilderService, route, dataService, notificationService) {
8939
- this.navBuilderService = navBuilderService;
8940
- this.route = route;
8941
- this.dataService = dataService;
8942
- this.notificationService = notificationService;
8943
- this.locationId$ = new BehaviorSubject('');
8944
- }
8945
- ngOnInit() {
8946
- this.items$ = combineLatest(this.navBuilderService.actionBarConfig$, this.locationId$).pipe(map(([items, locationId]) => items.filter(config => config.locationId === locationId)));
8947
- }
8948
- ngOnChanges(changes) {
8949
- if ('locationId' in changes) {
8950
- this.locationId$.next(changes['locationId'].currentValue);
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
+ }
8951
9109
  }
9110
+ // Make the selection mutable
9111
+ this._selection = this._selection.map(x => (Object.assign({}, x)));
8952
9112
  }
8953
- handleClick(event, item) {
8954
- if (typeof item.onClick === 'function') {
8955
- item.onClick(event, {
8956
- route: this.route,
8957
- dataService: this.dataService,
8958
- notificationService: this.notificationService,
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
+ }
9122
+ }
9123
+
9124
+ class ProductMultiSelectorDialogComponent {
9125
+ constructor(dataService, changeDetector) {
9126
+ this.dataService = dataService;
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);
9138
+ }
9139
+ ngOnInit() {
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' },
8959
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
+ }
8960
9196
  }
8961
9197
  }
8962
- getRouterLink(item) {
8963
- return this.navBuilderService.getRouterLink(item, this.route);
9198
+ trackByFn(index, item) {
9199
+ return item.productId;
8964
9200
  }
8965
- getButtonStyles(item) {
8966
- const styles = ['btn'];
8967
- if (item.buttonStyle && item.buttonStyle === 'link') {
8968
- styles.push('btn-link');
8969
- return styles;
8970
- }
8971
- styles.push(this.getButtonColorClass(item));
8972
- return styles;
9201
+ setSearchTerm(term) {
9202
+ this.searchTerm$.next(term);
8973
9203
  }
8974
- getButtonColorClass(item) {
8975
- switch (item.buttonColor) {
8976
- case undefined:
8977
- case 'primary':
8978
- return item.buttonStyle === 'outline' ? 'btn-outline' : 'btn-primary';
8979
- case 'success':
8980
- return item.buttonStyle === 'outline' ? 'btn-success-outline' : 'btn-success';
8981
- case 'warning':
8982
- return item.buttonStyle === 'outline' ? 'btn-warning-outline' : 'btn-warning';
8983
- default:
8984
- assertNever(item.buttonColor);
8985
- return '';
8986
- }
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();
8987
9233
  }
8988
9234
  }
8989
- ActionBarItemsComponent.decorators = [
9235
+ ProductMultiSelectorDialogComponent.decorators = [
8990
9236
  { type: Component, args: [{
8991
- selector: 'vdr-action-bar-items',
8992
- 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",
8993
9239
  changeDetection: ChangeDetectionStrategy.OnPush,
8994
- 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"]
8995
9241
  },] }
8996
9242
  ];
8997
- ActionBarItemsComponent.ctorParameters = () => [
8998
- { type: NavBuilderService },
8999
- { type: ActivatedRoute },
9243
+ ProductMultiSelectorDialogComponent.ctorParameters = () => [
9000
9244
  { type: DataService },
9001
- { type: NotificationService }
9002
- ];
9003
- ActionBarItemsComponent.propDecorators = {
9004
- locationId: [{ type: HostBinding, args: ['attr.data-location-id',] }, { type: Input }]
9005
- };
9245
+ { type: ChangeDetectorRef }
9246
+ ];
9006
9247
 
9007
- class ActionBarLeftComponent {
9008
- constructor() {
9009
- 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;
9010
9255
  }
9011
- }
9012
- ActionBarLeftComponent.decorators = [
9013
- { type: Component, args: [{
9014
- selector: 'vdr-ab-left',
9015
- template: `
9016
- <ng-content></ng-content>
9017
- `
9018
- },] }
9019
- ];
9020
- ActionBarLeftComponent.propDecorators = {
9021
- grow: [{ type: Input }]
9022
- };
9023
- class ActionBarRightComponent {
9024
- constructor() {
9025
- 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
+ });
9026
9275
  }
9027
9276
  }
9028
- ActionBarRightComponent.decorators = [
9277
+ ProductMultiSelectorFormInputComponent.id = 'product-multi-form-input';
9278
+ ProductMultiSelectorFormInputComponent.decorators = [
9029
9279
  { type: Component, args: [{
9030
- selector: 'vdr-ab-right',
9031
- template: `
9032
- <ng-content></ng-content>
9033
- `
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: [""]
9034
9284
  },] }
9035
9285
  ];
9036
- ActionBarRightComponent.propDecorators = {
9037
- grow: [{ type: Input }]
9038
- };
9039
- class ActionBarComponent {
9040
- }
9041
- ActionBarComponent.decorators = [
9042
- { type: Component, args: [{
9043
- selector: 'vdr-action-bar',
9044
- 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",
9045
- 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"]
9046
- },] }
9286
+ ProductMultiSelectorFormInputComponent.ctorParameters = () => [
9287
+ { type: ModalService },
9288
+ { type: DataService },
9289
+ { type: ChangeDetectorRef }
9047
9290
  ];
9048
- ActionBarComponent.propDecorators = {
9049
- left: [{ type: ContentChild, args: [ActionBarLeftComponent,] }],
9050
- right: [{ type: ContentChild, args: [ActionBarRightComponent,] }]
9291
+ ProductMultiSelectorFormInputComponent.propDecorators = {
9292
+ config: [{ type: Input }],
9293
+ formControl: [{ type: Input }],
9294
+ readonly: [{ type: Input }]
9051
9295
  };
9052
9296
 
9053
- class AddressFormComponent {
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
+ }
9054
9338
  }
9055
- AddressFormComponent.decorators = [
9339
+ ProductSelectorFormInputComponent.id = 'product-selector-form-input';
9340
+ ProductSelectorFormInputComponent.decorators = [
9056
9341
  { type: Component, args: [{
9057
- selector: 'vdr-address-form',
9058
- 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",
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",
9059
9344
  changeDetection: ChangeDetectionStrategy.OnPush,
9060
- styles: [""]
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"]
9061
9346
  },] }
9062
9347
  ];
9063
- AddressFormComponent.propDecorators = {
9064
- customFields: [{ type: Input }],
9065
- formGroup: [{ type: Input }],
9066
- availableCountries: [{ type: Input }]
9067
- };
9348
+ ProductSelectorFormInputComponent.ctorParameters = () => [
9349
+ { type: DataService }
9350
+ ];
9068
9351
 
9069
9352
  /**
9070
- * A wrapper around an <input> element which adds a prefix and/or a suffix element.
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
9071
9360
  */
9072
- class AffixedInputComponent {
9361
+ class RelationFormInputComponent {
9073
9362
  }
9074
- AffixedInputComponent.decorators = [
9363
+ RelationFormInputComponent.id = 'relation-form-input';
9364
+ RelationFormInputComponent.decorators = [
9075
9365
  { type: Component, args: [{
9076
- selector: 'vdr-affixed-input',
9077
- 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",
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",
9078
9368
  changeDetection: ChangeDetectionStrategy.OnPush,
9079
- 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"]
9369
+ styles: [":host{display:block;background-color:var(--color-component-bg-200);padding:3px}\n"]
9080
9370
  },] }
9081
9371
  ];
9082
- AffixedInputComponent.propDecorators = {
9083
- prefix: [{ type: Input }],
9084
- suffix: [{ type: Input }]
9372
+ RelationFormInputComponent.propDecorators = {
9373
+ readonly: [{ type: Input }]
9085
9374
  };
9086
9375
 
9087
9376
  /**
9088
- * A form input control which displays a number input with a percentage sign suffix.
9089
- */
9090
- class PercentageSuffixInputComponent {
9091
- constructor() {
9092
- this.disabled = false;
9093
- this.readonly = false;
9094
- }
9095
- ngOnChanges(changes) {
9096
- if ('value' in changes) {
9097
- this.writeValue(changes['value'].currentValue);
9098
- }
9099
- }
9100
- registerOnChange(fn) {
9101
- this.onChange = fn;
9102
- }
9103
- registerOnTouched(fn) {
9104
- this.onTouch = fn;
9105
- }
9106
- setDisabledState(isDisabled) {
9107
- this.disabled = isDisabled;
9108
- }
9109
- onInput(value) {
9110
- this.onChange(value);
9111
- }
9112
- writeValue(value) {
9113
- const numericValue = +value;
9114
- if (!Number.isNaN(numericValue)) {
9115
- this._value = numericValue;
9116
- }
9117
- }
9377
+ * @description
9378
+ * Uses the {@link RichTextEditorComponent} as in input for `text` type fields.
9379
+ *
9380
+ * @docsCategory custom-input-components
9381
+ * @docsPage default-inputs
9382
+ */
9383
+ class RichTextFormInputComponent {
9118
9384
  }
9119
- PercentageSuffixInputComponent.decorators = [
9385
+ RichTextFormInputComponent.id = 'rich-text-form-input';
9386
+ RichTextFormInputComponent.decorators = [
9120
9387
  { type: Component, args: [{
9121
- selector: 'vdr-percentage-suffix-input',
9122
- template: `
9123
- <vdr-affixed-input suffix="%">
9124
- <input
9125
- type="number"
9126
- step="1"
9127
- [value]="_value"
9128
- [disabled]="disabled"
9129
- [readonly]="readonly"
9130
- (input)="onInput($event.target.value)"
9131
- (focus)="onTouch()"
9132
- />
9133
- </vdr-affixed-input>
9134
- `,
9135
- providers: [
9136
- {
9137
- provide: NG_VALUE_ACCESSOR,
9138
- useExisting: PercentageSuffixInputComponent,
9139
- multi: true,
9140
- },
9141
- ],
9142
- styles: [`
9143
- :host {
9144
- padding: 0;
9145
- }
9146
- `]
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",
9390
+ changeDetection: ChangeDetectionStrategy.OnPush,
9391
+ styles: [":host textarea{resize:both;height:6rem;width:100%}\n"]
9147
9392
  },] }
9148
- ];
9149
- PercentageSuffixInputComponent.propDecorators = {
9150
- disabled: [{ type: Input }],
9151
- readonly: [{ type: Input }],
9152
- value: [{ type: Input }]
9153
- };
9393
+ ];
9154
9394
 
9155
9395
  /**
9156
- * A component for selecting files to upload as new Assets.
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
9157
9402
  */
9158
- class AssetFileInputComponent {
9159
- constructor(serverConfig) {
9160
- this.serverConfig = serverConfig;
9161
- /**
9162
- * CSS selector of the DOM element which will be masked by the file
9163
- * drop zone. Defaults to `body`.
9164
- */
9165
- this.dropZoneTarget = 'body';
9166
- this.uploading = false;
9167
- this.selectFiles = new EventEmitter();
9168
- this.dragging = false;
9169
- this.overDropZone = false;
9170
- this.dropZoneStyle = {
9171
- 'width.px': 0,
9172
- 'height.px': 0,
9173
- 'top.px': 0,
9174
- 'left.px': 0,
9175
- };
9176
- }
9177
- ngOnInit() {
9178
- this.accept = this.serverConfig.serverConfig.permittedAssetTypes.join(',');
9179
- this.fitDropZoneToTarget();
9180
- }
9181
- onDragEnter() {
9182
- this.dragging = true;
9183
- this.fitDropZoneToTarget();
9184
- }
9185
- // DragEvent is not supported in Safari, see https://github.com/vendure-ecommerce/vendure/pull/284
9186
- onDragLeave(event) {
9187
- if (!event.clientX && !event.clientY) {
9188
- this.dragging = false;
9189
- }
9190
- }
9191
- /**
9192
- * Preventing this event is required to make dropping work.
9193
- * See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API#Define_a_drop_zone
9194
- */
9195
- onDragOver(event) {
9196
- event.preventDefault();
9197
- }
9198
- // DragEvent is not supported in Safari, see https://github.com/vendure-ecommerce/vendure/pull/284
9199
- onDrop(event) {
9200
- event.preventDefault();
9201
- this.dragging = false;
9202
- this.overDropZone = false;
9203
- const files = Array.from(event.dataTransfer ? event.dataTransfer.items : [])
9204
- .map(i => i.getAsFile())
9205
- .filter(notNullOrUndefined);
9206
- this.selectFiles.emit(files);
9207
- }
9208
- select(event) {
9209
- const files = event.target.files;
9210
- if (files) {
9211
- this.selectFiles.emit(Array.from(files));
9212
- }
9213
- }
9214
- fitDropZoneToTarget() {
9215
- const target = document.querySelector(this.dropZoneTarget);
9216
- if (target) {
9217
- const rect = target.getBoundingClientRect();
9218
- this.dropZoneStyle['width.px'] = rect.width;
9219
- this.dropZoneStyle['height.px'] = rect.height;
9220
- this.dropZoneStyle['top.px'] = rect.top;
9221
- this.dropZoneStyle['left.px'] = rect.left;
9222
- }
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;
9223
9407
  }
9224
9408
  }
9225
- AssetFileInputComponent.decorators = [
9409
+ SelectFormInputComponent.id = 'select-form-input';
9410
+ SelectFormInputComponent.decorators = [
9226
9411
  { type: Component, args: [{
9227
- selector: 'vdr-asset-file-input',
9228
- 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",
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",
9229
9414
  changeDetection: ChangeDetectionStrategy.OnPush,
9230
- 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"]
9415
+ styles: ["select{width:100%}\n"]
9231
9416
  },] }
9232
9417
  ];
9233
- AssetFileInputComponent.ctorParameters = () => [
9234
- { type: ServerConfigService }
9235
- ];
9236
- AssetFileInputComponent.propDecorators = {
9237
- dropZoneTarget: [{ type: Input }],
9238
- uploading: [{ type: Input }],
9239
- selectFiles: [{ type: Output }],
9240
- onDragEnter: [{ type: HostListener, args: ['document:dragenter',] }],
9241
- onDragLeave: [{ type: HostListener, args: ['document:dragleave', ['$event'],] }]
9418
+ SelectFormInputComponent.propDecorators = {
9419
+ readonly: [{ type: Input }]
9242
9420
  };
9243
9421
 
9244
- class AssetPreviewDialogComponent {
9245
- constructor(dataService) {
9246
- this.dataService = dataService;
9247
- }
9248
- ngOnInit() {
9249
- this.assetWithTags$ = of(this.asset).pipe(mergeMap(asset => {
9250
- if (this.hasTags(asset)) {
9251
- return of(asset);
9252
- }
9253
- else {
9254
- // tslint:disable-next-line:no-non-null-assertion
9255
- return this.dataService.product.getAsset(asset.id).mapSingle(data => data.asset);
9256
- }
9257
- }));
9422
+ /**
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
9428
+ */
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;
9258
9433
  }
9259
- hasTags(asset) {
9260
- return asset.hasOwnProperty('tags');
9434
+ get suffix() {
9435
+ var _a;
9436
+ return ((_a = this.config.ui) === null || _a === void 0 ? void 0 : _a.suffix) || this.config.suffix;
9261
9437
  }
9262
9438
  }
9263
- AssetPreviewDialogComponent.decorators = [
9439
+ TextFormInputComponent.id = 'text-form-input';
9440
+ TextFormInputComponent.decorators = [
9264
9441
  { type: Component, args: [{
9265
- selector: 'vdr-asset-preview-dialog',
9266
- 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",
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",
9267
9444
  changeDetection: ChangeDetectionStrategy.OnPush,
9268
- styles: [":host{height:70vh}.update-button.hidden{visibility:hidden}\n"]
9445
+ styles: ["input{width:100%}\n"]
9269
9446
  },] }
9270
- ];
9271
- AssetPreviewDialogComponent.ctorParameters = () => [
9272
- { type: DataService }
9273
9447
  ];
9274
9448
 
9275
- class AssetGalleryComponent {
9276
- constructor(modalService) {
9277
- this.modalService = modalService;
9278
- /**
9279
- * If true, allows multiple assets to be selected by ctrl+clicking.
9280
- */
9281
- this.multiSelect = false;
9282
- this.canDelete = false;
9283
- this.selectionChange = new EventEmitter();
9284
- this.deleteAssets = new EventEmitter();
9285
- this.selection = [];
9286
- }
9287
- ngOnChanges() {
9288
- if (this.assets) {
9289
- for (const asset of this.selection) {
9290
- // Update and selected assets with any changes
9291
- const match = this.assets.find(a => a.id === asset.id);
9292
- if (match) {
9293
- Object.assign(asset, match);
9294
- }
9295
- }
9296
- }
9297
- }
9298
- toggleSelection(asset, event) {
9299
- const index = this.selection.findIndex(a => a.id === asset.id);
9300
- if (this.multiSelect && (event === null || event === void 0 ? void 0 : event.shiftKey) && 1 <= this.selection.length) {
9301
- const lastSelection = this.selection[this.selection.length - 1];
9302
- const lastSelectionIndex = this.assets.findIndex(a => a.id === lastSelection.id);
9303
- const currentIndex = this.assets.findIndex(a => a.id === asset.id);
9304
- const start = currentIndex < lastSelectionIndex ? currentIndex : lastSelectionIndex;
9305
- const end = currentIndex > lastSelectionIndex ? currentIndex + 1 : lastSelectionIndex;
9306
- this.selection.push(...this.assets.slice(start, end).filter(a => !this.selection.find(s => s.id === a.id)));
9307
- }
9308
- else if (index === -1) {
9309
- if (this.multiSelect && ((event === null || event === void 0 ? void 0 : event.ctrlKey) || (event === null || event === void 0 ? void 0 : event.shiftKey))) {
9310
- this.selection.push(asset);
9311
- }
9312
- else {
9313
- this.selection = [asset];
9314
- }
9315
- }
9316
- else {
9317
- if (this.multiSelect && (event === null || event === void 0 ? void 0 : event.ctrlKey)) {
9318
- this.selection.splice(index, 1);
9319
- }
9320
- else if (1 < this.selection.length) {
9321
- this.selection = [asset];
9322
- }
9323
- else {
9324
- this.selection.splice(index, 1);
9325
- }
9326
- }
9327
- // Make the selection mutable
9328
- this.selection = this.selection.map(x => (Object.assign({}, x)));
9329
- this.selectionChange.emit(this.selection);
9330
- }
9331
- selectMultiple(assets) {
9332
- this.selection = assets;
9333
- this.selectionChange.emit(this.selection);
9334
- }
9335
- isSelected(asset) {
9336
- return !!this.selection.find(a => a.id === asset.id);
9337
- }
9338
- lastSelected() {
9339
- return this.selection[this.selection.length - 1];
9340
- }
9341
- previewAsset(asset) {
9342
- this.modalService
9343
- .fromComponent(AssetPreviewDialogComponent, {
9344
- size: 'xl',
9345
- closable: true,
9346
- locals: { asset },
9347
- })
9348
- .subscribe();
9349
- }
9350
- entityInfoClick(event) {
9351
- event.preventDefault();
9352
- event.stopPropagation();
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;
9353
9459
  }
9354
9460
  }
9355
- AssetGalleryComponent.decorators = [
9461
+ TextareaFormInputComponent.id = 'textarea-form-input';
9462
+ TextareaFormInputComponent.decorators = [
9356
9463
  { type: Component, args: [{
9357
- selector: 'vdr-asset-gallery',
9358
- 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",
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",
9359
9466
  changeDetection: ChangeDetectionStrategy.OnPush,
9360
- 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"]
9467
+ styles: [":host textarea{resize:both;height:6rem;width:100%}\n"]
9361
9468
  },] }
9362
- ];
9363
- AssetGalleryComponent.ctorParameters = () => [
9364
- { type: ModalService }
9365
- ];
9366
- AssetGalleryComponent.propDecorators = {
9367
- assets: [{ type: Input }],
9368
- multiSelect: [{ type: Input }],
9369
- canDelete: [{ type: Input }],
9370
- selectionChange: [{ type: Output }],
9371
- deleteAssets: [{ type: Output }]
9372
- };
9469
+ ];
9373
9470
 
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,
9488
+ ];
9374
9489
  /**
9375
9490
  * @description
9376
- * 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.
9377
9494
  *
9378
9495
  * @example
9379
9496
  * ```TypeScript
9380
- * selectAssets() {
9381
- * this.modalService
9382
- * .fromComponent(AssetPickerDialogComponent, {
9383
- * size: 'xl',
9384
- * })
9385
- * .subscribe(result => {
9386
- * if (result && result.length) {
9387
- * // ...
9388
- * }
9389
- * });
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
+ * }
9390
9522
  * }
9391
9523
  * ```
9392
9524
  *
9393
- * @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
9394
9535
  */
9395
- class AssetPickerDialogComponent {
9396
- 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;
9397
9590
  this.dataService = dataService;
9398
9591
  this.notificationService = notificationService;
9399
- this.paginationConfig = {
9400
- currentPage: 1,
9401
- itemsPerPage: 25,
9402
- totalItems: 1,
9403
- };
9404
- this.multiSelect = true;
9405
- this.initialTags = [];
9406
- this.selection = [];
9407
- this.searchTerm$ = new BehaviorSubject(undefined);
9408
- this.filterByTags$ = new BehaviorSubject(undefined);
9409
- this.uploading = false;
9410
- this.destroy$ = new Subject();
9592
+ this.locationId$ = new BehaviorSubject('');
9411
9593
  }
9412
9594
  ngOnInit() {
9413
- this.listQuery = this.dataService.product.getAssetList(this.paginationConfig.itemsPerPage, 0);
9414
- this.allTags$ = this.dataService.product.getTagList().mapSingle(data => data.tags.items);
9415
- this.assets$ = this.listQuery.stream$.pipe(tap(result => (this.paginationConfig.totalItems = result.assets.totalItems)), map(result => result.assets.items));
9416
- this.searchTerm$.pipe(debounceTime(250), takeUntil(this.destroy$)).subscribe(() => {
9417
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9418
- });
9419
- this.filterByTags$.pipe(debounceTime(100), takeUntil(this.destroy$)).subscribe(() => {
9420
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9421
- });
9595
+ this.items$ = combineLatest(this.navBuilderService.actionBarConfig$, this.locationId$).pipe(map(([items, locationId]) => items.filter(config => config.locationId === locationId)));
9422
9596
  }
9423
- ngAfterViewInit() {
9424
- if (0 < this.initialTags.length) {
9425
- this.allTags$
9426
- .pipe(take(1), map(allTags => allTags.filter(tag => this.initialTags.includes(tag.value))), tap(tags => this.filterByTags$.next(tags)), delay(1))
9427
- .subscribe(tags => this.assetSearchInputComponent.setTags(tags));
9597
+ ngOnChanges(changes) {
9598
+ if ('locationId' in changes) {
9599
+ this.locationId$.next(changes['locationId'].currentValue);
9428
9600
  }
9429
9601
  }
9430
- ngOnDestroy() {
9431
- this.destroy$.next();
9432
- this.destroy$.complete();
9433
- }
9434
- pageChange(page) {
9435
- this.paginationConfig.currentPage = page;
9436
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9437
- }
9438
- itemsPerPageChange(itemsPerPage) {
9439
- this.paginationConfig.itemsPerPage = itemsPerPage;
9440
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
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
+ }
9441
9610
  }
9442
- cancel() {
9443
- this.resolveWith();
9611
+ getRouterLink(item) {
9612
+ return this.navBuilderService.getRouterLink(item, this.route);
9444
9613
  }
9445
- select() {
9446
- this.resolveWith(this.selection);
9614
+ getButtonStyles(item) {
9615
+ const styles = ['btn'];
9616
+ if (item.buttonStyle && item.buttonStyle === 'link') {
9617
+ styles.push('btn-link');
9618
+ return styles;
9619
+ }
9620
+ styles.push(this.getButtonColorClass(item));
9621
+ return styles;
9447
9622
  }
9448
- createAssets(files) {
9449
- if (files.length) {
9450
- this.uploading = true;
9451
- this.dataService.product
9452
- .createAssets(files)
9453
- .pipe(finalize(() => (this.uploading = false)))
9454
- .subscribe(res => {
9455
- this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9456
- this.notificationService.success(marker('asset.notify-create-assets-success'), {
9457
- count: files.length,
9458
- });
9459
- const assets = res.createAssets.filter(a => a.__typename === 'Asset');
9460
- this.assetGalleryComponent.selectMultiple(assets);
9461
- });
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 '';
9462
9635
  }
9463
9636
  }
9464
- fetchPage(currentPage, itemsPerPage) {
9465
- var _a;
9466
- const take = +itemsPerPage;
9467
- const skip = (currentPage - 1) * +itemsPerPage;
9468
- const searchTerm = this.searchTerm$.value;
9469
- const tags = (_a = this.filterByTags$.value) === null || _a === void 0 ? void 0 : _a.map(t => t.value);
9470
- this.listQuery.ref.refetch({
9471
- options: {
9472
- skip,
9473
- take,
9474
- filter: {
9475
- name: {
9476
- contains: searchTerm,
9477
- },
9478
- },
9479
- sort: {
9480
- createdAt: SortOrder.DESC,
9481
- },
9482
- tags,
9483
- tagsOperator: LogicalOperator.AND,
9484
- },
9485
- });
9486
- }
9487
9637
  }
9488
- AssetPickerDialogComponent.decorators = [
9638
+ ActionBarItemsComponent.decorators = [
9489
9639
  { type: Component, args: [{
9490
- selector: 'vdr-asset-picker-dialog',
9491
- 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",
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",
9492
9642
  changeDetection: ChangeDetectionStrategy.OnPush,
9493
- 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"]
9643
+ styles: [":host{display:inline-block;min-height:36px}\n"]
9494
9644
  },] }
9495
9645
  ];
9496
- AssetPickerDialogComponent.ctorParameters = () => [
9646
+ ActionBarItemsComponent.ctorParameters = () => [
9647
+ { type: NavBuilderService },
9648
+ { type: ActivatedRoute },
9497
9649
  { type: DataService },
9498
9650
  { type: NotificationService }
9499
9651
  ];
9500
- AssetPickerDialogComponent.propDecorators = {
9501
- assetSearchInputComponent: [{ type: ViewChild, args: ['assetSearchInputComponent',] }],
9502
- assetGalleryComponent: [{ type: ViewChild, args: ['assetGalleryComponent',] }]
9652
+ ActionBarItemsComponent.propDecorators = {
9653
+ locationId: [{ type: HostBinding, args: ['attr.data-location-id',] }, { type: Input }]
9503
9654
  };
9504
9655
 
9505
- class AssetPreviewLinksComponent {
9656
+ class ActionBarLeftComponent {
9506
9657
  constructor() {
9507
- this.sizes = ['tiny', 'thumb', 'small', 'medium', 'large', 'full'];
9658
+ this.grow = false;
9508
9659
  }
9509
9660
  }
9510
- AssetPreviewLinksComponent.decorators = [
9661
+ ActionBarLeftComponent.decorators = [
9511
9662
  { type: Component, args: [{
9512
- selector: 'vdr-asset-preview-links',
9513
- 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",
9514
9708
  changeDetection: ChangeDetectionStrategy.OnPush,
9515
- styles: [".asset-preview-link{font-size:12px}\n"]
9709
+ styles: [""]
9516
9710
  },] }
9517
9711
  ];
9518
- AssetPreviewLinksComponent.propDecorators = {
9519
- asset: [{ type: Input }]
9712
+ AddressFormComponent.propDecorators = {
9713
+ customFields: [{ type: Input }],
9714
+ formGroup: [{ type: Input }],
9715
+ availableCountries: [{ type: Input }]
9520
9716
  };
9521
9717
 
9522
- class ManageTagsDialogComponent {
9523
- constructor(dataService) {
9524
- this.dataService = dataService;
9525
- this.toDelete = [];
9526
- this.toUpdate = [];
9527
- }
9528
- ngOnInit() {
9529
- this.allTags$ = this.dataService.product.getTagList().mapStream(data => data.tags.items);
9530
- }
9531
- toggleDelete(id) {
9532
- const marked = this.markedAsDeleted(id);
9533
- if (marked) {
9534
- this.toDelete = this.toDelete.filter(_id => _id !== id);
9535
- }
9536
- else {
9537
- this.toDelete.push(id);
9538
- }
9539
- }
9540
- markedAsDeleted(id) {
9541
- return this.toDelete.includes(id);
9542
- }
9543
- updateTagValue(id, value) {
9544
- const exists = this.toUpdate.find(i => i.id === id);
9545
- if (exists) {
9546
- exists.value = value;
9547
- }
9548
- else {
9549
- this.toUpdate.push({ id, value });
9550
- }
9551
- }
9552
- saveChanges() {
9553
- const operations = [];
9554
- for (const id of this.toDelete) {
9555
- operations.push(this.dataService.product.deleteTag(id));
9556
- }
9557
- for (const item of this.toUpdate) {
9558
- if (!this.toDelete.includes(item.id)) {
9559
- operations.push(this.dataService.product.updateTag(item));
9560
- }
9561
- }
9562
- return forkJoin(operations).subscribe(() => this.resolveWith(true));
9563
- }
9718
+ /**
9719
+ * A wrapper around an <input> element which adds a prefix and/or a suffix element.
9720
+ */
9721
+ class AffixedInputComponent {
9564
9722
  }
9565
- ManageTagsDialogComponent.decorators = [
9723
+ AffixedInputComponent.decorators = [
9566
9724
  { type: Component, args: [{
9567
- selector: 'vdr-manage-tags-dialog',
9568
- 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",
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",
9569
9727
  changeDetection: ChangeDetectionStrategy.OnPush,
9570
- 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"]
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"]
9571
9729
  },] }
9572
9730
  ];
9573
- ManageTagsDialogComponent.ctorParameters = () => [
9574
- { type: DataService }
9575
- ];
9731
+ AffixedInputComponent.propDecorators = {
9732
+ prefix: [{ type: Input }],
9733
+ suffix: [{ type: Input }]
9734
+ };
9576
9735
 
9577
- class AssetPreviewComponent {
9578
- constructor(formBuilder, dataService, notificationService, changeDetector, modalService) {
9579
- this.formBuilder = formBuilder;
9580
- this.dataService = dataService;
9581
- this.notificationService = notificationService;
9582
- this.changeDetector = changeDetector;
9583
- this.modalService = modalService;
9584
- this.editable = false;
9585
- this.customFields = [];
9586
- this.assetChange = new EventEmitter();
9587
- this.editClick = new EventEmitter();
9588
- this.size = 'medium';
9589
- this.width = 0;
9590
- this.height = 0;
9591
- this.centered = true;
9592
- this.settingFocalPoint = false;
9593
- }
9594
- get fpx() {
9595
- return this.asset.focalPoint ? this.asset.focalPoint.x : null;
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;
9596
9743
  }
9597
- get fpy() {
9598
- return this.asset.focalPoint ? this.asset.focalPoint.y : null;
9744
+ ngOnChanges(changes) {
9745
+ if ('value' in changes) {
9746
+ this.writeValue(changes['value'].currentValue);
9747
+ }
9599
9748
  }
9600
- ngOnInit() {
9601
- var _a;
9602
- const { focalPoint } = this.asset;
9603
- this.form = this.formBuilder.group({
9604
- name: [this.asset.name],
9605
- tags: [(_a = this.asset.tags) === null || _a === void 0 ? void 0 : _a.map(t => t.value)],
9606
- });
9607
- this.subscription = this.form.valueChanges.subscribe(value => {
9608
- this.assetChange.emit({
9609
- id: this.asset.id,
9610
- name: value.name,
9611
- tags: value.tags,
9612
- });
9613
- });
9614
- this.subscription.add(fromEvent(window, 'resize')
9615
- .pipe(debounceTime(50))
9616
- .subscribe(() => {
9617
- this.updateDimensions();
9618
- this.changeDetector.markForCheck();
9619
- }));
9749
+ registerOnChange(fn) {
9750
+ this.onChange = fn;
9620
9751
  }
9621
- ngOnDestroy() {
9622
- if (this.subscription) {
9623
- this.subscription.unsubscribe();
9624
- }
9752
+ registerOnTouched(fn) {
9753
+ this.onTouch = fn;
9625
9754
  }
9626
- getSourceFileName() {
9627
- const parts = this.asset.source.split('/');
9628
- return parts[parts.length - 1];
9755
+ setDisabledState(isDisabled) {
9756
+ this.disabled = isDisabled;
9629
9757
  }
9630
- onImageLoad() {
9631
- this.updateDimensions();
9632
- this.changeDetector.markForCheck();
9758
+ onInput(value) {
9759
+ this.onChange(value);
9633
9760
  }
9634
- updateDimensions() {
9635
- const img = this.imageElementRef.nativeElement;
9636
- const container = this.previewDivRef.nativeElement;
9637
- const imgWidth = img.naturalWidth;
9638
- const imgHeight = img.naturalHeight;
9639
- const containerWidth = container.offsetWidth;
9640
- const containerHeight = container.offsetHeight;
9641
- const constrainToContainer = this.settingFocalPoint;
9642
- if (constrainToContainer) {
9643
- const controlsMarginPx = 48 * 2;
9644
- const availableHeight = containerHeight - controlsMarginPx;
9645
- const availableWidth = containerWidth;
9646
- const hRatio = imgHeight / availableHeight;
9647
- const wRatio = imgWidth / availableWidth;
9648
- const imageExceedsAvailableDimensions = 1 < hRatio || 1 < wRatio;
9649
- if (imageExceedsAvailableDimensions) {
9650
- const factor = hRatio < wRatio ? wRatio : hRatio;
9651
- this.width = Math.round(imgWidth / factor);
9652
- this.height = Math.round(imgHeight / factor);
9653
- this.centered = true;
9654
- return;
9655
- }
9761
+ writeValue(value) {
9762
+ const numericValue = +value;
9763
+ if (!Number.isNaN(numericValue)) {
9764
+ this._value = numericValue;
9656
9765
  }
9657
- this.width = imgWidth;
9658
- this.height = imgHeight;
9659
- this.centered = imgWidth <= containerWidth && imgHeight <= containerHeight;
9660
9766
  }
9661
- setFocalPointStart() {
9662
- this.sizePriorToSettingFocalPoint = this.size;
9663
- this.size = 'medium';
9664
- this.settingFocalPoint = true;
9665
- this.lastFocalPoint = this.asset.focalPoint || { x: 0.5, y: 0.5 };
9666
- this.updateDimensions();
9767
+ }
9768
+ PercentageSuffixInputComponent.decorators = [
9769
+ { type: Component, args: [{
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
+ `]
9796
+ },] }
9797
+ ];
9798
+ PercentageSuffixInputComponent.propDecorators = {
9799
+ disabled: [{ type: Input }],
9800
+ readonly: [{ type: Input }],
9801
+ value: [{ type: Input }]
9802
+ };
9803
+
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
+ };
9667
9825
  }
9668
- removeFocalPoint() {
9669
- this.dataService.product
9670
- .updateAsset({
9671
- id: this.asset.id,
9672
- focalPoint: null,
9673
- })
9674
- .subscribe(() => {
9675
- this.notificationService.success(marker('asset.update-focal-point-success'));
9676
- this.asset = Object.assign(Object.assign({}, this.asset), { focalPoint: null });
9677
- this.changeDetector.markForCheck();
9678
- }, () => this.notificationService.error(marker('asset.update-focal-point-error')));
9826
+ ngOnInit() {
9827
+ this.accept = this.serverConfig.serverConfig.permittedAssetTypes.join(',');
9828
+ this.fitDropZoneToTarget();
9679
9829
  }
9680
- onFocalPointChange(point) {
9681
- this.lastFocalPoint = point;
9830
+ onDragEnter() {
9831
+ this.dragging = true;
9832
+ this.fitDropZoneToTarget();
9682
9833
  }
9683
- setFocalPointCancel() {
9684
- this.settingFocalPoint = false;
9685
- this.lastFocalPoint = undefined;
9686
- this.size = this.sizePriorToSettingFocalPoint;
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;
9838
+ }
9687
9839
  }
9688
- setFocalPointEnd() {
9689
- this.settingFocalPoint = false;
9690
- this.size = this.sizePriorToSettingFocalPoint;
9691
- if (this.lastFocalPoint) {
9692
- const { x, y } = this.lastFocalPoint;
9693
- this.lastFocalPoint = undefined;
9694
- this.dataService.product
9695
- .updateAsset({
9696
- id: this.asset.id,
9697
- focalPoint: { x, y },
9698
- })
9699
- .subscribe(() => {
9700
- this.notificationService.success(marker('asset.update-focal-point-success'));
9701
- this.asset = Object.assign(Object.assign({}, this.asset), { focalPoint: { x, y } });
9702
- this.changeDetector.markForCheck();
9703
- }, () => this.notificationService.error(marker('asset.update-focal-point-error')));
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();
9846
+ }
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);
9856
+ }
9857
+ select(event) {
9858
+ const files = event.target.files;
9859
+ if (files) {
9860
+ this.selectFiles.emit(Array.from(files));
9704
9861
  }
9705
9862
  }
9706
- manageTags() {
9707
- this.modalService
9708
- .fromComponent(ManageTagsDialogComponent, {
9709
- size: 'sm',
9710
- })
9711
- .subscribe(result => {
9712
- if (result) {
9713
- this.notificationService.success(marker('common.notify-updated-tags-success'));
9714
- }
9715
- });
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
+ }
9716
9872
  }
9717
9873
  }
9718
- AssetPreviewComponent.decorators = [
9874
+ AssetFileInputComponent.decorators = [
9719
9875
  { type: Component, args: [{
9720
- selector: 'vdr-asset-preview',
9721
- 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 #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",
9722
9878
  changeDetection: ChangeDetectionStrategy.OnPush,
9723
- 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 .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"]
9724
9880
  },] }
9725
9881
  ];
9726
- AssetPreviewComponent.ctorParameters = () => [
9727
- { type: FormBuilder },
9728
- { type: DataService },
9729
- { type: NotificationService },
9730
- { type: ChangeDetectorRef },
9731
- { type: ModalService }
9882
+ AssetFileInputComponent.ctorParameters = () => [
9883
+ { type: ServerConfigService }
9732
9884
  ];
9733
- AssetPreviewComponent.propDecorators = {
9734
- asset: [{ type: Input }],
9735
- editable: [{ type: Input }],
9736
- customFields: [{ type: Input }],
9737
- customFieldsForm: [{ type: Input }],
9738
- assetChange: [{ type: Output }],
9739
- editClick: [{ type: Output }],
9740
- imageElementRef: [{ type: ViewChild, args: ['imageElement', { static: true },] }],
9741
- 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'],] }]
9742
9891
  };
9743
9892
 
9744
- /**
9745
- * A custom SelectionModel for the NgSelect component which only allows a single
9746
- * search term at a time.
9747
- */
9748
- class SingleSearchSelectionModel {
9749
- constructor() {
9750
- this._selected = [];
9751
- }
9752
- get value() {
9753
- return this._selected;
9893
+ class AssetPreviewDialogComponent {
9894
+ constructor(dataService) {
9895
+ this.dataService = dataService;
9754
9896
  }
9755
- select(item, multiple, groupAsModel) {
9756
- item.selected = true;
9757
- if (groupAsModel || !item.children) {
9758
- if (item.value.label) {
9759
- const isSearchTerm = (i) => !!i.value.label;
9760
- const searchTerms = this._selected.filter(isSearchTerm);
9761
- if (searchTerms.length > 0) {
9762
- // there is already a search term, so replace it with this new one.
9763
- this._selected = this._selected.filter(i => !isSearchTerm(i)).concat(item);
9764
- }
9765
- else {
9766
- this._selected.push(item);
9767
- }
9897
+ ngOnInit() {
9898
+ this.assetWithTags$ = of(this.asset).pipe(mergeMap(asset => {
9899
+ if (this.hasTags(asset)) {
9900
+ return of(asset);
9768
9901
  }
9769
9902
  else {
9770
- 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);
9771
9905
  }
9772
- }
9773
- }
9774
- unselect(item, multiple) {
9775
- this._selected = this._selected.filter(x => x !== item);
9776
- item.selected = false;
9777
- }
9778
- clear(keepDisabled) {
9779
- this._selected = keepDisabled ? this._selected.filter(x => x.disabled) : [];
9780
- }
9781
- _setChildrenSelectedState(children, selected) {
9782
- children.forEach(x => (x.selected = selected));
9783
- }
9784
- _removeChildren(parent) {
9785
- this._selected = this._selected.filter(x => x.parent !== parent);
9906
+ }));
9786
9907
  }
9787
- _removeParent(parent) {
9788
- this._selected = this._selected.filter(x => x !== parent);
9908
+ hasTags(asset) {
9909
+ return asset.hasOwnProperty('tags');
9789
9910
  }
9790
9911
  }
9791
- function SingleSearchSelectionModelFactory() {
9792
- return new SingleSearchSelectionModel();
9793
- }
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
+ ];
9794
9923
 
9795
- const ɵ0 = SingleSearchSelectionModelFactory;
9796
- class AssetSearchInputComponent {
9797
- constructor() {
9798
- this.searchTermChange = new EventEmitter();
9799
- this.tagsChange = new EventEmitter();
9800
- this.lastTerm = '';
9801
- this.lastTagIds = [];
9802
- this.filterTagResults = (term, item) => {
9803
- if (!this.isTag(item)) {
9804
- return false;
9805
- }
9806
- return item.value.toLowerCase().startsWith(term.toLowerCase());
9807
- };
9808
- this.isTag = (input) => {
9809
- return typeof input === 'object' && !!input && input.hasOwnProperty('value');
9810
- };
9811
- }
9812
- setSearchTerm(term) {
9813
- if (term) {
9814
- this.selectComponent.select({ label: term, value: { label: term } });
9815
- }
9816
- else {
9817
- const currentTerm = this.selectComponent.selectedItems.find(i => !this.isTag(i.value));
9818
- if (currentTerm) {
9819
- this.selectComponent.unselect(currentTerm);
9820
- }
9821
- }
9822
- }
9823
- setTags(tags) {
9824
- const items = this.selectComponent.items;
9825
- this.selectComponent.selectedItems.forEach(item => {
9826
- if (this.isTag(item.value) && !tags.map(t => t.id).includes(item.id)) {
9827
- this.selectComponent.unselect(item);
9828
- }
9829
- });
9830
- tags.map(tag => {
9831
- return items === null || items === void 0 ? void 0 : items.find(item => this.isTag(item) && item.id === tag.id);
9832
- })
9833
- .filter(notNullOrUndefined)
9834
- .forEach(item => {
9835
- const isSelected = this.selectComponent.selectedItems.find(i => {
9836
- const val = i.value;
9837
- if (this.isTag(val)) {
9838
- return val.id === item.id;
9839
- }
9840
- return false;
9841
- });
9842
- if (!isSelected) {
9843
- this.selectComponent.select({ label: '', value: item });
9844
- }
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,
9845
9938
  });
9846
9939
  }
9847
- onSelectChange(selectedItems) {
9848
- if (!Array.isArray(selectedItems)) {
9849
- selectedItems = [selectedItems];
9850
- }
9851
- const searchTermItems = selectedItems.filter(item => !this.isTag(item));
9852
- if (1 < searchTermItems.length) {
9853
- for (let i = 0; i < searchTermItems.length - 1; i++) {
9854
- // this.selectComponent.unselect(searchTermItems[i] as any);
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
+ }
9855
9948
  }
9856
9949
  }
9857
- const searchTermItem = searchTermItems[searchTermItems.length - 1];
9858
- const searchTerm = searchTermItem ? searchTermItem.label : '';
9859
- const tags = selectedItems.filter(this.isTag);
9860
- if (searchTerm !== this.lastTerm) {
9861
- this.searchTermChange.emit(searchTerm);
9862
- this.lastTerm = searchTerm;
9950
+ if (changes['assets']) {
9951
+ this.selectionManager.setCurrentItems(this.assets);
9863
9952
  }
9864
- if (this.lastTagIds.join(',') !== tags.map(t => t.id).join(',')) {
9865
- this.tagsChange.emit(tags);
9866
- this.lastTagIds = tags.map(t => t.id);
9953
+ if (changes['multiSelect']) {
9954
+ this.selectionManager.setMultiSelect(this.multiSelect);
9867
9955
  }
9868
9956
  }
9869
- isSearchHeaderSelected() {
9870
- return this.selectComponent.itemsList.markedIndex === -1;
9957
+ toggleSelection(asset, event) {
9958
+ this.selectionManager.toggleSelection(asset, event);
9959
+ this.selectionChange.emit(this.selectionManager.selection);
9871
9960
  }
9872
- addTagFn(item) {
9873
- return { label: item };
9961
+ selectMultiple(assets) {
9962
+ this.selectionManager.selectMultiple(assets);
9963
+ this.selectionChange.emit(this.selectionManager.selection);
9964
+ }
9965
+ isSelected(asset) {
9966
+ return this.selectionManager.isSelected(asset);
9967
+ }
9968
+ lastSelected() {
9969
+ return this.selectionManager.lastSelected();
9970
+ }
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();
9874
9983
  }
9875
9984
  }
9876
- AssetSearchInputComponent.decorators = [
9985
+ AssetGalleryComponent.decorators = [
9877
9986
  { type: Component, args: [{
9878
- selector: 'vdr-asset-search-input',
9879
- 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",
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",
9880
9989
  changeDetection: ChangeDetectionStrategy.OnPush,
9881
- providers: [{ provide: SELECTION_MODEL_FACTORY, useValue: ɵ0 }],
9882
- 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"]
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"]
9883
9991
  },] }
9884
9992
  ];
9885
- AssetSearchInputComponent.propDecorators = {
9886
- tags: [{ type: Input }],
9887
- searchTermChange: [{ type: Output }],
9888
- tagsChange: [{ type: Output }],
9889
- selectComponent: [{ type: ViewChild, args: ['selectComponent', { static: true },] }]
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 }]
9890
10002
  };
9891
10003
 
9892
- class ChannelAssignmentControlComponent {
9893
- constructor(dataService) {
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) {
9894
10027
  this.dataService = dataService;
9895
- this.multiple = true;
9896
- this.includeDefaultChannel = true;
9897
- this.disableChannelIds = [];
9898
- this.value = [];
9899
- this.disabled = false;
10028
+ this.notificationService = notificationService;
10029
+ this.paginationConfig = {
10030
+ currentPage: 1,
10031
+ itemsPerPage: 25,
10032
+ totalItems: 1,
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();
9900
10041
  }
9901
10042
  ngOnInit() {
9902
- this.channels$ = this.dataService.client.userStatus().single$.pipe(map(({ userStatus }) => userStatus.channels.filter(c => this.includeDefaultChannel ? true : c.code !== DEFAULT_CHANNEL_CODE)), tap(channels => {
9903
- if (!this.channels) {
9904
- this.channels = channels;
9905
- this.mapIncomingValueToChannels(this.lastIncomingValue);
9906
- }
9907
- else {
9908
- this.channels = channels;
9909
- }
9910
- }));
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);
10048
+ });
10049
+ this.filterByTags$.pipe(debounceTime(100), takeUntil(this.destroy$)).subscribe(() => {
10050
+ this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
10051
+ });
9911
10052
  }
9912
- registerOnChange(fn) {
9913
- this.onChange = fn;
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));
10058
+ }
9914
10059
  }
9915
- registerOnTouched(fn) {
9916
- this.onTouched = fn;
10060
+ ngOnDestroy() {
10061
+ this.destroy$.next();
10062
+ this.destroy$.complete();
9917
10063
  }
9918
- setDisabledState(isDisabled) {
9919
- this.disabled = isDisabled;
10064
+ pageChange(page) {
10065
+ this.paginationConfig.currentPage = page;
10066
+ this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9920
10067
  }
9921
- writeValue(obj) {
9922
- this.lastIncomingValue = obj;
9923
- this.mapIncomingValueToChannels(obj);
10068
+ itemsPerPageChange(itemsPerPage) {
10069
+ this.paginationConfig.itemsPerPage = itemsPerPage;
10070
+ this.fetchPage(this.paginationConfig.currentPage, this.paginationConfig.itemsPerPage);
9924
10071
  }
9925
- focussed() {
9926
- if (this.onTouched) {
9927
- this.onTouched();
9928
- }
10072
+ cancel() {
10073
+ this.resolveWith();
9929
10074
  }
9930
- channelIsDisabled(id) {
9931
- return this.disableChannelIds.includes(id);
10075
+ select() {
10076
+ this.resolveWith(this.selection);
9932
10077
  }
9933
- valueChanged(value) {
9934
- if (Array.isArray(value)) {
9935
- this.onChange(value.map(c => c.id));
9936
- }
9937
- else {
9938
- this.onChange([value ? value.id : undefined]);
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
+ });
9939
10092
  }
9940
10093
  }
9941
- compareFn(c1, c2) {
9942
- const c1id = typeof c1 === 'string' ? c1 : c1.id;
9943
- const c2id = typeof c2 === 'string' ? c2 : c2.id;
9944
- return c1id === c2id;
9945
- }
9946
- mapIncomingValueToChannels(value) {
10094
+ fetchPage(currentPage, itemsPerPage) {
9947
10095
  var _a;
9948
- if (Array.isArray(value)) {
9949
- if (typeof value[0] === 'string') {
9950
- this.value = value
9951
- .map(id => { var _a; return (_a = this.channels) === null || _a === void 0 ? void 0 : _a.find(c => c.id === id); })
9952
- .filter(notNullOrUndefined);
9953
- }
9954
- else {
9955
- this.value = value;
9956
- }
9957
- }
9958
- else {
9959
- if (typeof value === 'string') {
9960
- const channel = (_a = this.channels) === null || _a === void 0 ? void 0 : _a.find(c => c.id === value);
9961
- if (channel) {
9962
- this.value = [channel];
9963
- }
9964
- }
9965
- else if (value && value.id) {
9966
- this.value = [value];
9967
- }
9968
- }
9969
- }
9970
- }
9971
- ChannelAssignmentControlComponent.decorators = [
9972
- { type: Component, args: [{
9973
- selector: 'vdr-channel-assignment-control',
9974
- 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",
9975
- changeDetection: ChangeDetectionStrategy.Default,
9976
- providers: [
9977
- {
9978
- provide: NG_VALUE_ACCESSOR,
9979
- useExisting: ChannelAssignmentControlComponent,
9980
- multi: true,
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,
9981
10107
  },
9982
- ],
9983
- 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"]
9984
- },] }
9985
- ];
9986
- ChannelAssignmentControlComponent.ctorParameters = () => [
9987
- { type: DataService }
9988
- ];
9989
- ChannelAssignmentControlComponent.propDecorators = {
9990
- multiple: [{ type: Input }],
9991
- includeDefaultChannel: [{ type: Input }],
9992
- disableChannelIds: [{ type: Input }]
9993
- };
9994
-
9995
- class ChannelBadgeComponent {
9996
- get isDefaultChannel() {
9997
- return this.channelCode === DEFAULT_CHANNEL_CODE;
10108
+ },
10109
+ sort: {
10110
+ createdAt: SortOrder.DESC,
10111
+ },
10112
+ tags,
10113
+ tagsOperator: LogicalOperator.AND,
10114
+ },
10115
+ });
9998
10116
  }
9999
10117
  }
10000
- ChannelBadgeComponent.decorators = [
10118
+ AssetPickerDialogComponent.decorators = [
10001
10119
  { type: Component, args: [{
10002
- selector: 'vdr-channel-badge',
10003
- template: "<clr-icon shape=\"layers\" [style.color]=\"isDefaultChannel ? '#aaa' : (channelCode | stringToColor)\"></clr-icon>\r\n",
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",
10004
10122
  changeDetection: ChangeDetectionStrategy.OnPush,
10005
- styles: [":host{display:inline-block}button :host{margin-bottom:-1px}clr-icon{margin-right:6px}\n"]
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"]
10006
10124
  },] }
10007
10125
  ];
10008
- ChannelBadgeComponent.propDecorators = {
10009
- channelCode: [{ type: Input }]
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',] }]
10010
10133
  };
10011
10134
 
10012
- /**
10013
- * @description
10014
- * A chip component for displaying a label with an optional action icon.
10015
- *
10016
- * @example
10017
- * ```HTML
10018
- * <vdr-chip [colorFrom]="item.value"
10019
- * icon="close"
10020
- * (iconClick)="clear(item)">
10021
- * {{ item.value }}</vdr-chip>
10022
- * ```
10023
- * @docsCategory components
10024
- */
10025
- class ChipComponent {
10135
+ class AssetPreviewLinksComponent {
10026
10136
  constructor() {
10027
- this.invert = false;
10028
- /**
10029
- * @description
10030
- * If set, the chip will have an auto-generated background
10031
- * color based on the string value passed in.
10032
- */
10033
- this.colorFrom = '';
10034
- this.iconClick = new EventEmitter();
10137
+ this.sizes = ['tiny', 'thumb', 'small', 'medium', 'large', 'full'];
10035
10138
  }
10036
10139
  }
10037
- ChipComponent.decorators = [
10140
+ AssetPreviewLinksComponent.decorators = [
10038
10141
  { type: Component, args: [{
10039
- selector: 'vdr-chip',
10040
- 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",
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",
10041
10144
  changeDetection: ChangeDetectionStrategy.OnPush,
10042
- 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"]
10145
+ styles: [".asset-preview-link{font-size:12px}\n"]
10043
10146
  },] }
10044
10147
  ];
10045
- ChipComponent.propDecorators = {
10046
- icon: [{ type: Input }],
10047
- invert: [{ type: Input }],
10048
- colorFrom: [{ type: Input }],
10049
- colorType: [{ type: Input }],
10050
- iconClick: [{ type: Output }]
10148
+ AssetPreviewLinksComponent.propDecorators = {
10149
+ asset: [{ type: Input }]
10051
10150
  };
10052
10151
 
10053
- /**
10054
- * ConfigArg values are always stored as strings. If they are not primitives, then
10055
- * they are JSON-encoded. This function unwraps them back into their original
10056
- * data type.
10057
- */
10058
- function getConfigArgValue(value) {
10059
- try {
10060
- return value ? JSON.parse(value) : undefined;
10061
- }
10062
- catch (e) {
10063
- return value;
10064
- }
10065
- }
10066
- function encodeConfigArgValue(value) {
10067
- return Array.isArray(value) ? JSON.stringify(value) : (value !== null && value !== void 0 ? value : '').toString();
10068
- }
10069
- /**
10070
- * Creates an empty ConfigurableOperation object based on the definition.
10071
- */
10072
- function configurableDefinitionToInstance(def) {
10073
- return Object.assign(Object.assign({}, def), { args: def.args.map(arg => {
10074
- return Object.assign(Object.assign({}, arg), { value: getDefaultConfigArgValue(arg) });
10075
- }) });
10076
- }
10077
- /**
10078
- * Converts an object of the type:
10079
- * ```
10080
- * {
10081
- * code: 'my-operation',
10082
- * args: {
10083
- * someProperty: 'foo'
10084
- * }
10085
- * }
10086
- * ```
10087
- * to the format defined by the ConfigurableOperationInput GraphQL input type:
10088
- * ```
10089
- * {
10090
- * code: 'my-operation',
10091
- * args: [
10092
- * { name: 'someProperty', value: 'foo' }
10093
- * ]
10094
- * }
10095
- * ```
10096
- */
10097
- function toConfigurableOperationInput(operation, formValueOperations) {
10098
- return {
10099
- code: operation.code,
10100
- arguments: Object.values(formValueOperations.args || {}).map((value, j) => ({
10101
- name: operation.args[j].name,
10102
- value: value.hasOwnProperty('value')
10103
- ? encodeConfigArgValue(value.value)
10104
- : encodeConfigArgValue(value),
10105
- })),
10106
- };
10107
- }
10108
- function configurableOperationValueIsValid(def, value) {
10109
- if (!def || !value) {
10110
- return false;
10152
+ class ManageTagsDialogComponent {
10153
+ constructor(dataService) {
10154
+ this.dataService = dataService;
10155
+ this.toDelete = [];
10156
+ this.toUpdate = [];
10111
10157
  }
10112
- if (def.code !== value.code) {
10113
- return false;
10158
+ ngOnInit() {
10159
+ this.allTags$ = this.dataService.product.getTagList().mapStream(data => data.tags.items);
10114
10160
  }
10115
- for (const argDef of def.args) {
10116
- const argVal = value.args[argDef.name];
10117
- if (argDef.required && (argVal == null || argVal === '' || argVal === '0')) {
10118
- return false;
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);
10119
10168
  }
10120
10169
  }
10121
- return true;
10122
- }
10123
- /**
10124
- * Returns a default value based on the type of the config arg.
10125
- */
10126
- function getDefaultConfigArgValue(arg) {
10127
- var _a;
10128
- return arg.list ? [] : (_a = arg.defaultValue) !== null && _a !== void 0 ? _a : null;
10129
- }
10130
-
10131
- /**
10132
- * Interpolates the description of an ConfigurableOperation with the given values.
10133
- */
10134
- function interpolateDescription(operation, values) {
10135
- if (!operation) {
10136
- return '';
10170
+ markedAsDeleted(id) {
10171
+ return this.toDelete.includes(id);
10137
10172
  }
10138
- const templateString = operation.description;
10139
- const interpolated = templateString.replace(/{\s*([a-zA-Z0-9]+)\s*}/gi, (substring, argName) => {
10140
- const normalizedArgName = argName.toLowerCase();
10141
- const value = values[normalizedArgName];
10142
- if (value == null) {
10143
- return '_';
10144
- }
10145
- let formatted = value;
10146
- const argDef = operation.args.find(arg => arg.name === normalizedArgName);
10147
- if (argDef && argDef.type === 'int' && argDef.ui && argDef.ui.component === 'currency-form-input') {
10148
- formatted = value / 100;
10173
+ updateTagValue(id, value) {
10174
+ const exists = this.toUpdate.find(i => i.id === id);
10175
+ if (exists) {
10176
+ exists.value = value;
10149
10177
  }
10150
- if (argDef && argDef.type === 'datetime' && value instanceof Date) {
10151
- formatted = value.toLocaleDateString();
10178
+ else {
10179
+ this.toUpdate.push({ id, value });
10152
10180
  }
10153
- return formatted;
10154
- });
10155
- return interpolated;
10156
- }
10157
-
10158
- /**
10159
- * A form input which renders a card with the internal form fields of the given ConfigurableOperation.
10160
- */
10161
- class ConfigurableInputComponent {
10162
- constructor() {
10163
- this.readonly = false;
10164
- this.removable = true;
10165
- this.remove = new EventEmitter();
10166
- this.argValues = {};
10167
- this.form = new FormGroup({});
10168
10181
  }
10169
- interpolateDescription() {
10170
- if (this.operationDefinition) {
10171
- return interpolateDescription(this.operationDefinition, this.form.value);
10182
+ saveChanges() {
10183
+ const operations = [];
10184
+ for (const id of this.toDelete) {
10185
+ operations.push(this.dataService.product.deleteTag(id));
10172
10186
  }
10173
- else {
10174
- return '';
10187
+ for (const item of this.toUpdate) {
10188
+ if (!this.toDelete.includes(item.id)) {
10189
+ operations.push(this.dataService.product.updateTag(item));
10190
+ }
10175
10191
  }
10192
+ return forkJoin(operations).subscribe(() => this.resolveWith(true));
10176
10193
  }
10177
- ngOnChanges(changes) {
10178
- if ('operation' in changes || 'operationDefinition' in changes) {
10179
- this.createForm();
10180
- }
10194
+ }
10195
+ ManageTagsDialogComponent.decorators = [
10196
+ { type: Component, args: [{
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"]
10201
+ },] }
10202
+ ];
10203
+ ManageTagsDialogComponent.ctorParameters = () => [
10204
+ { type: DataService }
10205
+ ];
10206
+
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
+ }));
10181
10250
  }
10182
10251
  ngOnDestroy() {
10183
10252
  if (this.subscription) {
10184
10253
  this.subscription.unsubscribe();
10185
10254
  }
10186
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
+ });
10346
+ }
10347
+ }
10348
+ AssetPreviewComponent.decorators = [
10349
+ { type: Component, args: [{
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",
10352
+ changeDetection: ChangeDetectionStrategy.OnPush,
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"]
10354
+ },] }
10355
+ ];
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 },] }]
10372
+ };
10373
+
10374
+ /**
10375
+ * A custom SelectionModel for the NgSelect component which only allows a single
10376
+ * search term at a time.
10377
+ */
10378
+ class SingleSearchSelectionModel {
10379
+ constructor() {
10380
+ this._selected = [];
10381
+ }
10382
+ get value() {
10383
+ return this._selected;
10384
+ }
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
+ }
10402
+ }
10403
+ }
10404
+ unselect(item, multiple) {
10405
+ this._selected = this._selected.filter(x => x !== item);
10406
+ item.selected = false;
10407
+ }
10408
+ clear(keepDisabled) {
10409
+ this._selected = keepDisabled ? this._selected.filter(x => x.disabled) : [];
10410
+ }
10411
+ _setChildrenSelectedState(children, selected) {
10412
+ children.forEach(x => (x.selected = selected));
10413
+ }
10414
+ _removeChildren(parent) {
10415
+ this._selected = this._selected.filter(x => x.parent !== parent);
10416
+ }
10417
+ _removeParent(parent) {
10418
+ this._selected = this._selected.filter(x => x !== parent);
10419
+ }
10420
+ }
10421
+ function SingleSearchSelectionModelFactory() {
10422
+ return new SingleSearchSelectionModel();
10423
+ }
10424
+
10425
+ const ɵ0$1 = SingleSearchSelectionModelFactory;
10426
+ class AssetSearchInputComponent {
10427
+ constructor() {
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
+ };
10441
+ }
10442
+ setSearchTerm(term) {
10443
+ if (term) {
10444
+ this.selectComponent.select({ label: term, value: { label: term } });
10445
+ }
10446
+ else {
10447
+ const currentTerm = this.selectComponent.selectedItems.find(i => !this.isTag(i.value));
10448
+ if (currentTerm) {
10449
+ this.selectComponent.unselect(currentTerm);
10450
+ }
10451
+ }
10452
+ }
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
+ });
10476
+ }
10477
+ onSelectChange(selectedItems) {
10478
+ if (!Array.isArray(selectedItems)) {
10479
+ selectedItems = [selectedItems];
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
+ }));
10541
+ }
10187
10542
  registerOnChange(fn) {
10188
10543
  this.onChange = fn;
10189
10544
  }
10190
10545
  registerOnTouched(fn) {
10191
- this.onTouch = fn;
10546
+ this.onTouched = fn;
10192
10547
  }
10193
10548
  setDisabledState(isDisabled) {
10194
- if (isDisabled) {
10195
- this.form.disable();
10196
- }
10197
- else {
10198
- this.form.enable();
10199
- }
10549
+ this.disabled = isDisabled;
10200
10550
  }
10201
- writeValue(value) {
10202
- if (value) {
10203
- this.form.patchValue(value);
10204
- }
10551
+ writeValue(obj) {
10552
+ this.lastIncomingValue = obj;
10553
+ this.mapIncomingValueToChannels(obj);
10205
10554
  }
10206
- trackByName(index, arg) {
10207
- return arg.name;
10555
+ focussed() {
10556
+ if (this.onTouched) {
10557
+ this.onTouched();
10558
+ }
10208
10559
  }
10209
- getArgDef(arg) {
10210
- var _a;
10211
- 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);
10212
10562
  }
10213
- createForm() {
10214
- var _a, _b;
10215
- if (!this.operation) {
10216
- return;
10563
+ valueChanged(value) {
10564
+ if (Array.isArray(value)) {
10565
+ this.onChange(value.map(c => c.id));
10217
10566
  }
10218
- if (this.subscription) {
10219
- this.subscription.unsubscribe();
10567
+ else {
10568
+ this.onChange([value ? value.id : undefined]);
10220
10569
  }
10221
- this.form = new FormGroup({});
10222
- this.form.__id = Math.random().toString(36).substr(10);
10223
- if (this.operation.args) {
10224
- for (const arg of ((_a = this.operationDefinition) === null || _a === void 0 ? void 0 : _a.args) || []) {
10225
- let value = (_b = this.operation.args.find(a => a.name === arg.name)) === null || _b === void 0 ? void 0 : _b.value;
10226
- if (value === undefined) {
10227
- value = getDefaultConfigArgValue(arg);
10228
- }
10229
- const validators = arg.list ? undefined : arg.required ? Validators.required : undefined;
10230
- 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;
10231
10586
  }
10232
10587
  }
10233
- this.subscription = this.form.valueChanges.subscribe(value => {
10234
- if (this.onChange) {
10235
- this.onChange({
10236
- code: this.operation && this.operation.code,
10237
- args: value,
10238
- });
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
+ }
10239
10594
  }
10240
- if (this.onTouch) {
10241
- this.onTouch();
10595
+ else if (value && value.id) {
10596
+ this.value = [value];
10242
10597
  }
10243
- });
10244
- }
10245
- validate(c) {
10246
- if (this.form.invalid) {
10247
- return {
10248
- required: true,
10249
- };
10250
10598
  }
10251
- return null;
10252
10599
  }
10253
10600
  }
10254
- ConfigurableInputComponent.decorators = [
10601
+ ChannelAssignmentControlComponent.decorators = [
10255
10602
  { type: Component, args: [{
10256
- selector: 'vdr-configurable-input',
10257
- 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",
10258
- 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,
10259
10606
  providers: [
10260
10607
  {
10261
10608
  provide: NG_VALUE_ACCESSOR,
10262
- useExisting: ConfigurableInputComponent,
10263
- multi: true,
10264
- },
10265
- {
10266
- provide: NG_VALIDATORS,
10267
- useExisting: forwardRef(() => ConfigurableInputComponent),
10609
+ useExisting: ChannelAssignmentControlComponent,
10268
10610
  multi: true,
10269
10611
  },
10270
10612
  ],
10271
- 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"]
10272
10614
  },] }
10273
10615
  ];
10274
- ConfigurableInputComponent.propDecorators = {
10275
- operation: [{ type: Input }],
10276
- operationDefinition: [{ type: Input }],
10277
- readonly: [{ type: Input }],
10278
- removable: [{ type: Input }],
10279
- 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 }]
10280
10681
  };
10281
10682
 
10282
10683
  /**
@@ -11899,7 +12300,7 @@ HelpTooltipComponent.decorators = [
11899
12300
  selector: 'vdr-help-tooltip',
11900
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",
11901
12302
  changeDetection: ChangeDetectionStrategy.OnPush,
11902
- styles: [""]
12303
+ styles: ["clr-tooltip{display:inline-flex}\n"]
11903
12304
  },] }
11904
12305
  ];
11905
12306
  HelpTooltipComponent.propDecorators = {
@@ -12161,6 +12562,105 @@ PaginationControlsComponent.propDecorators = {
12161
12562
  pageChange: [{ type: Output }]
12162
12563
  };
12163
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
+
12164
12664
  /**
12165
12665
  * @description
12166
12666
  * A component for selecting product variants via an autocomplete-style select input.
@@ -12961,7 +13461,7 @@ RichTextEditorComponent.decorators = [
12961
13461
  },
12962
13462
  ProsemirrorService,
12963
13463
  ],
12964
- 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"]
12965
13465
  },] }
12966
13466
  ];
12967
13467
  RichTextEditorComponent.ctorParameters = () => [
@@ -12982,6 +13482,7 @@ class SelectToggleComponent {
12982
13482
  constructor() {
12983
13483
  this.size = 'large';
12984
13484
  this.selected = false;
13485
+ this.hiddenWhenOff = false;
12985
13486
  this.disabled = false;
12986
13487
  this.selectedChange = new EventEmitter();
12987
13488
  }
@@ -12989,14 +13490,15 @@ class SelectToggleComponent {
12989
13490
  SelectToggleComponent.decorators = [
12990
13491
  { type: Component, args: [{
12991
13492
  selector: 'vdr-select-toggle',
12992
- 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",
12993
13494
  changeDetection: ChangeDetectionStrategy.OnPush,
12994
- 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"]
12995
13496
  },] }
12996
13497
  ];
12997
13498
  SelectToggleComponent.propDecorators = {
12998
13499
  size: [{ type: Input }],
12999
13500
  selected: [{ type: Input }],
13501
+ hiddenWhenOff: [{ type: Input }],
13000
13502
  disabled: [{ type: Input }],
13001
13503
  label: [{ type: Input }],
13002
13504
  selectedChange: [{ type: Output }]
@@ -13750,7 +14252,7 @@ class RelationAssetInputComponent {
13750
14252
  this.dataService = dataService;
13751
14253
  }
13752
14254
  ngOnInit() {
13753
- 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 => {
13754
14256
  if (id) {
13755
14257
  return this.dataService.product.getAsset(id).mapStream(data => data.asset || undefined);
13756
14258
  }
@@ -13769,14 +14271,14 @@ class RelationAssetInputComponent {
13769
14271
  })
13770
14272
  .subscribe(result => {
13771
14273
  if (result && result.length) {
13772
- this.parentFormControl.setValue(result[0]);
13773
- this.parentFormControl.markAsDirty();
14274
+ this.formControl.setValue(result[0]);
14275
+ this.formControl.markAsDirty();
13774
14276
  }
13775
14277
  });
13776
14278
  }
13777
14279
  remove() {
13778
- this.parentFormControl.setValue(null);
13779
- this.parentFormControl.markAsDirty();
14280
+ this.formControl.setValue(null);
14281
+ this.formControl.markAsDirty();
13780
14282
  }
13781
14283
  previewAsset(asset) {
13782
14284
  this.modalService
@@ -13788,6 +14290,7 @@ class RelationAssetInputComponent {
13788
14290
  .subscribe();
13789
14291
  }
13790
14292
  }
14293
+ RelationAssetInputComponent.id = 'asset-form-input';
13791
14294
  RelationAssetInputComponent.decorators = [
13792
14295
  { type: Component, args: [{
13793
14296
  selector: 'vdr-relation-asset-input',
@@ -13802,7 +14305,7 @@ RelationAssetInputComponent.ctorParameters = () => [
13802
14305
  ];
13803
14306
  RelationAssetInputComponent.propDecorators = {
13804
14307
  readonly: [{ type: Input }],
13805
- parentFormControl: [{ type: Input }],
14308
+ formControl: [{ type: Input, args: ['parentFormControl',] }],
13806
14309
  config: [{ type: Input }]
13807
14310
  };
13808
14311
 
@@ -14992,6 +15495,8 @@ const DECLARATIONS = [
14992
15495
  UiExtensionPointComponent,
14993
15496
  CustomDetailComponentHostComponent,
14994
15497
  AssetPreviewLinksComponent,
15498
+ ProductMultiSelectorDialogComponent,
15499
+ ProductSearchInputComponent,
14995
15500
  ];
14996
15501
  const DYNAMIC_FORM_INPUTS = [
14997
15502
  TextFormInputComponent,
@@ -15015,6 +15520,8 @@ const DYNAMIC_FORM_INPUTS = [
15015
15520
  TextareaFormInputComponent,
15016
15521
  RichTextFormInputComponent,
15017
15522
  JsonEditorFormInputComponent,
15523
+ ProductMultiSelectorFormInputComponent,
15524
+ CombinationModeFormInputComponent,
15018
15525
  ];
15019
15526
  class SharedModule {
15020
15527
  }
@@ -15552,7 +16059,7 @@ function patchObject(obj, patch) {
15552
16059
  }
15553
16060
 
15554
16061
  // Auto-generated by the set-version.js script.
15555
- const ADMIN_UI_VERSION = '1.5.0';
16062
+ const ADMIN_UI_VERSION = '1.6.0';
15556
16063
 
15557
16064
  /**
15558
16065
  * Responsible for registering dashboard widget components and querying for layouts.
@@ -15772,5 +16279,5 @@ function unicodePatternValidator(patternRe) {
15772
16279
  * Generated bundle index. Do not edit.
15773
16280
  */
15774
16281
 
15775
- 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 };
15776
16283
  //# sourceMappingURL=vendure-admin-ui-core.js.map